[CTS2019]珍珠

link

难度

思维难度 : 四星

代码难度 : 三星

标签

反演 + 计数 + 数学 + 生成函数 + fftfft

题解

cntxcnt_x 表示第 xx 种颜色的珍珠个数。求有多少种分配 cntxcnt_x 的方案数,使 i=1Dcnti2m\sum_{i=1}^{D} \lfloor \frac{cnt_i}{2} \rfloor \geq m

发现这个式子很难求,所有推一下:

i=1Dcnti2mi=1Dcnticnti%22mi=1Dcnti%22mn(i=1Dcnti=n)i=1Dcnti%2n2m\sum_{i=1}^{D} \lfloor \frac{cnt_i}{2} \rfloor \geq m\\ \sum_{i=1}^{D} cnt_i - cnt_i \% 2 \geq2 m\\ \sum_{i=1}^{D} - cnt_i \%2 \geq 2m - n \quad (因为 \sum_{i=1}^{D}cnt_i=n)\\ \sum_{i=1}^{D} cnt_i \%2 \leq n - 2m

这样就只需要求至多有 n2mn - 2m 种颜色的珍珠有奇数个的方案数, 求出恰好有 ii (in2m)(i \leq n- 2m) 种颜色的珍珠有奇数个的方案数,然后加起来.

用二项式反演, 设 fif_i 表示恰好有 ii 种颜色的珍珠有奇数个的方案数,gig_i 表示钦定有 ii 种颜色的珍珠有奇数个的方案数.

fk=i=kn(ik)(1)ikgi=1k!i=kn(1)iki!(ik)!gif_k = \sum_{i = k}^{n}\tbinom{i}{k} (-1)^{i-k} g_i\\ =\frac{1}{k!}\sum_{i=k}^{n}(-1)^{i-k}\frac{i!}{(i-k)!}g_i

发现这是一个减法卷积,得到 gig_i 后就可以求出 fif_i 了.
做减法卷积,如 hk=i=knfigikh_k = \sum_{i=k}^{n}f_ig_{i-k} , 把 gikg_{i-k} 变成 gn(ik)g_{n - (i - k)} 就可以做加法卷积了. 相应的,原来的 hkh_khn+kh_{n+k}

然后就是求 gig_i
先考虑令钦定的 ii 种颜色的珍珠有奇数个的方案数, 用 EGFEGF.
[xk]exex2[x^k]\frac{e^x - e^{-x}}{2} 的意义就是对于一种珍珠,一共有 kk 个时的方案数.

那么 [xk](exex2)p[x^k](\frac{e^x - e^{-x}}{2})^p 的意义就是对于 pp 种珍珠,一共有 kk 个时的方案数.

剩下的个数任意,就是 (ex)Dp(e^x) ^ {D - p}

于是有:

gk=(Dk)n![xn](exex2)k(ex)Dkgk=(Dk)12kn![xn](exex)k(ex)Dkgk=(Dk)12kn![xn]i=0k(ki)exi(1)kiex(ki)ex(Dk)gk=(Dk)12kn!i=0k(ki)(1)ki[xn]exiex(ki)ex(Dk)gk=(Dk)12kn!i=0k(ki)(1)ki[xn]e(D2(ki))xgk=(Dk)12ki=0k(ki)(1)ki(D2(ki))ngk=D!(Dk)!12ki=0k1i!(ki)!(1)ki(D2(ki))ngk=D!(Dk)!12ki=0k1i!(ki)!(1)i(D2i)ngk=D!(Dk)!12ki=0k1i!(1)i(D2i)n1(ki)!g_k = \tbinom{D}{k} n![x^n] (\frac{e^x - e^{-x}}{2})^k (e^x)^{D-k}\\ g_k = \tbinom{D}{k} \frac{1}{2^k}n![x^n] (e^x - e^{-x})^k (e^x)^{D-k}\\ g_k = \tbinom{D}{k} \frac{1}{2^k}n![x^n] \sum_{i=0}^{k}\tbinom{k}{i}e^{xi}(-1)^{k-i}e^{-x(k-i)} e^{x(D-k)}\\ g_k = \tbinom{D}{k} \frac{1}{2^k}n! \sum_{i=0}^{k}\tbinom{k}{i}(-1)^{k-i}[x^n]e^{xi}e^{-x(k-i)} e^{x(D-k)}\\ g_k = \tbinom{D}{k} \frac{1}{2^k}n! \sum_{i=0}^{k}\tbinom{k}{i}(-1)^{k-i}[x^n]e^{(D-2(k-i))x}\\ g_k = \tbinom{D}{k} \frac{1}{2^k}\sum_{i=0}^{k}\tbinom{k}{i}(-1)^{k-i}(D-2(k-i))^n\\ g_k = \frac{D!}{(D-k)!} \frac{1}{2^k}\sum_{i=0}^{k}\frac{1}{i!(k-i)!}(-1)^{k-i}(D-2(k-i))^n\\ g_k = \frac{D!}{(D-k)!} \frac{1}{2^k}\sum_{i=0}^{k}\frac{1}{i!(k-i)!}(-1)^{i}(D-2i)^n\\ g_k = \frac{D!}{(D-k)!} \frac{1}{2^k}\sum_{i=0}^{k}\frac{1}{i!}(-1)^{i}(D-2i)^n\frac{1}{(k-i)!}\\

发现后面的东西是一个卷积,卷起来就好了

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long

using namespace std;

const int N = 1e6 + 10, mod = 998244353, G = 3;
int invG;
int tr[N];
int n, m, D;
int fac[N], ifac[N], inv[N];
int f[N], g[N];

int qpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}

int add(int a, int b) {
return a + b >= mod ? a + b - mod : a + b;
}

int sub(int a, int b) {
return a - b < 0 ? a - b + mod : a - b;
}

void ntt(int *f, int n, int op) {
for (int i = 0; i < n; ++i) {
tr[i] = (tr[i >> 1] >> 1) | ((i & 1) ? (n >> 1) : 0);
}
for (int i = 0; i < n; ++i) {
if (i < tr[i]) swap(f[i], f[tr[i]]);
}
for (int p = 2; p <= n; p <<= 1) {
int len = p / 2;
int tG = qpow(op ? G : invG, (mod - 1) / p);
for (int l = 0; l < n; l += p) {
int buf = 1;
for (int k = l; k < l + len; ++k) {
int tmp = buf * f[k + len] % mod;
f[k + len] = sub(f[k], tmp);
f[k] = add(f[k], tmp);
buf = buf * tG % mod;
}
}
}
int invn = qpow(n, mod - 2);
if (op == 0) {
for (int i = 0; i < n; ++i) f[i] = f[i] * invn % mod;
}
}

void px(int *f, int *g, int n) {
for (int i = 0; i < n; ++i) f[i] = f[i] * g[i] % mod;
}

void mul(int *f, int *g, int n) {
int m = n;
for (n = 1; n <= m * 2; n <<= 1);
fill(f + m, f + n, 0);
fill(g + m, g + n, 0);
ntt(f, n, 1), ntt(g, n, 1);
px(f, g, n);
ntt(f, n, 0);
}


signed main() {
cin >> D >> n >> m;
if (n - 2 * m < 0) return 0;
if (n - 2 * m >= D) return cout << qpow(D, n), 0;
fac[1] = fac[0] = ifac[1] = ifac[0] = inv[1] = 1;
invG = qpow(G, mod - 2);
for (int i = 2; i <= D + 1; ++i) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
ifac[i] = ifac[i - 1] * inv[i] % mod;
}
for (int i = 0; i <= D; ++i) {
if (i & 1) {
f[i] = sub(mod, qpow(sub(D, 2 * i), n) * ifac[i] % mod);
}
else {
f[i] = qpow(sub(D, 2 * i), n) * ifac[i] % mod;
}
g[i] = ifac[i];
}
mul(f, g, D + 1);
for (int i = 0; i <= D; ++i) {
f[i] = f[i] * fac[D] % mod * qpow(qpow(2, i), mod - 2) % mod * ifac[D - i] % mod * fac[i] % mod;
}
for (int i = 0; i <= D; ++i) {
if (i & 1) {
g[D - i] = sub(mod, ifac[i]);
}
else g[D - i] = ifac[i];
}
mul(f, g, D + 1);
int ans = 0;
for (int i = 0; i <= n - 2 * m; ++i) {
ans = add(ans, f[D + i] * ifac[i] % mod);
}
cout << ans << endl;
return 0;
}

总结

  1. 减法卷积可以通过翻转多项式来转化成加法卷积
  2. exe^x 全是 11exe^{-x} 奇数位是 1-1 , 偶数位是 11exex2\frac{e^x - e^{-x}}{2} , 奇数位是 11 , 偶数位是 00