[CTS2019]珍珠 NTT 生成函数

[CTS2019]珍珠

题目传送门:
luogu

分析

考虑每种颜色有几个。假设已经求出了每种颜色的个数为 d 1 , d 2 ⋯ d D d_1,d_2\cdots d_D d1,d2dD
方案数就是 n ! d 1 ! d 2 ! ⋯ d D ! \frac{n!}{d_1!d_2!\cdots d_D!} d1!d2!dD!n!
考虑这个方案合法的要求。
∑ d i ( m o d    2 ) ≤ n − 2 m \sum d_i (\mod 2) \le n-2m di(mod2)n2m
这是个蛮显然的转化,因为相同颜色两两匹配,那么不能匹配的就是奇数个的哪一个。
考虑求出 g k g_k gk ∑ d i ( m o d    2 ) = k \sum d_i (\mod 2)=k di(mod2)=k的方案数,答案就是 ∑ x = 0 n − 2 m g k \sum_{x=0}^{n-2m} g_k x=0n2mgk
容易想到EGF。
我们考虑随便放 D D D个随机变量的方案数的EGF表示方法:
D n = ⌊ x n ⌋ e D x n ! D^n=\lfloor x_n\rfloor e^{Dx}n! Dn=xneDxn!
什么意思呢?
e x = ∑ i = 0 ∞ x i i ! e^x=\sum_{i=0}^{\infty}\frac{x^i}{i!} ex=i=0i!xi,也就是说当前颜色可以放 1 , 2 , ⋯ 1,2,\cdots 1,2,个,并且消除内部顺序的影响,那么放置 D D D个实际上就是卷积 D D D次后的 n n n次幂系数。
如今我们知道有 k k k个要放置的位置是奇数个的。
如何表示只放置奇数个?
∑ i = 0 ∞ x 2 i ( 2 i ) ! = e x − e − x 2 \sum_{i=0}^{\infty}\frac{x^{2i}}{(2i)!}=\frac{e^x-e^{-x}}{2} i=0(2i)!x2i=2exex
偶数个同理为 e x + e − x 2 \frac{e^x+e^{-x}}{2} 2ex+ex
然后再用 C D k C_D^k CDk选出这若干个位置。
因此可以得到
g k = n ! ⌊ x n ⌋ C D k ( e x − e − x 2 ) k ( e x + e − x 2 ) D − k g_k=n!\lfloor x_n\rfloor C_D^k(\frac{e^x-e^{-x}}{2})^k (\frac{e^x+e^{-x}}{2})^{D-k} gk=n!xnCDk(2exex)k(2ex+ex)Dk
⌊ x n ⌋ \lfloor x_n\rfloor xn这个东西表示取第 n n n项系数
这个东西实际上可以直接推,但是比较麻烦。我们退而求其次,先求至少 k k k个奇数的答案:
f k = n ! ⌊ x n ⌋ C D k ( e x − e − x 2 ) k e ( D − k ) x f_k=n!\lfloor x_n\rfloor C_D^k(\frac{e^x-e^{-x}}{2})^k e^{(D-k)x} fk=n!xnCDk(2exex)ke(Dk)x
然后可以得到 f i = ∑ j = i D C j i g j f_i=\sum_{j=i}^DC_j^ig_j fi=j=iDCjigj
二项式反演一下可以得到 g i = ∑ j = i D ( − 1 ) j − i C j i f j g_i=\sum_{j=i}^D(-1)^{j-i}C_j^if_j gi=j=iD(1)jiCjifj
这个东西可以拆组合数用 N T T NTT NTT解决。
然后问题转化为求 f f f
暴力二项式展开: ( e x − e − x ) k = ∑ i = 0 k C k i ( − 1 ) i e − i x e ( k − i ) x (e^x-e^{-x})^k=\sum_{i=0}^kC_k^i(-1)^ie^{-ix}e^{(k-i)x} (exex)k=i=0kCki(1)ieixe(ki)x
然后得到
f k = n ! ⌊ x n ⌋ C D k 2 − k ∑ i = 0 k C k i ( − 1 ) i e ( k − 2 i ) x e ( D − k ) x = n ! ⌊ x n ⌋ C D k 2 − k ∑ i = 0 k C k i ( − 1 ) i e ( D − 2 i ) x f_k=n!\lfloor x_n\rfloor C_D^k2^{-k}\sum_{i=0}^kC_k^i(-1)^ie^{(k-2i)x}e^{(D-k)x}=n!\lfloor x_n\rfloor C_D^k2^{-k}\sum_{i=0}^kC_k^i(-1)^ie^{(D-2i)x} fk=n!xnCDk2ki=0kCki(1)ie(k2i)xe(Dk)x=n!xnCDk2ki=0kCki(1)ie(D2i)x
注意到
e ( D − 2 i ) x ⌊ x n ⌋ = ( D − 2 i ) n n ! e^{(D-2i)x}\lfloor x_n\rfloor=\frac{(D-2i)^n}{n!} e(D2i)xxn=n!(D2i)n
所以 f k = n ! C D k 2 − k ∑ i = 0 k C k i ( − 1 ) i ( D − 2 i ) n n ! = C D k 2 − k k ! ∑ i = 0 k 1 ( k − i ) ! ( − 1 ) i ( D − 2 i ) n i ! f_k=n!C_D^k2^{-k}\sum_{i=0}^kC_k^i(-1)^i\frac{(D-2i)^n}{n!}=C_D^k2^{-k}k!\sum_{i=0}^k\frac{1}{(k-i)!}(-1)^i\frac{(D-2i)^n}{i!} fk=n!CDk2ki=0kCki(1)in!(D2i)n=CDk2kk!i=0k(ki)!1(1)ii!(D2i)n
∑ \sum 前面的那坨只和 k k k有关系不用管,后面是多项式 ( − 1 ) i ( D − 2 i ) n i ! x i (-1)^i\frac{(D-2i)^n}{i!}x^i (1)ii!(D2i)nxi 1 i ! x i \frac{1}{i!}x^i i!1xi卷积结果的第 k k k项。
N T T NTT NTT即可
难点在于:1.问题的转化。2.指数型生成函数的转化。3.二项式反演的简化。
其实我觉得第3个是最难的,因为很容易直接走上直接肝式子的不归路(虽然有人肝出来了)。前面两个比较套路吧,主要还是得有模型积累。

代码

#include
const int N = 262144, P = 998244353;
int ri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f  = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int A[N], B[N], R[N], f[N], fac[N], ivf[N], w[N], L, D, n, m, InvL;
int Pow(int x, int k) {
    int r = 1;
    for(;k; k >>= 1, x = 1LL * x * x % P)
        if(k & 1)
            r = 1LL * r * x % P;
    return r;
}
int C(int m, int n) {return 1LL * fac[m] * ivf[n] % P * ivf[m - n] % P;}
void Pre(int m) {
    int x = 0; L = 1;
    for(;(L <<= 1) <= m; ++x) ;
    for(int i = 1;i < L; ++i)
        R[i] = R[i >> 1] >> 1 | (i & 1) << x;
    int wn = Pow(3, (P - 1) / L); w[0] = 1;
    for(int i = 1;i < L; ++i)
        w[i] = 1LL * w[i - 1] * wn % P;
    InvL = Pow(L, P - 2);
}
void NTT(int *F) {
    for(int i = 0;i < L; ++i)
        if(R[i] > i)
            std::swap(F[i], F[R[i]]);
    for(int i = 1, d = L >> 1; i < L; i <<= 1, d >>= 1)
        for(int j = 0;j < L; j += i << 1) {
            int *l = F + j, *r = F + i + j, *p = w, tp;
            for(int k = i; k--; ++l, ++r, p += d)
                tp = 1LL * *p * *r % P, *r = (*l - tp) % P, *l = (*l + tp) % P;
        }
}
int main() {
    D = ri(); n = ri(); m = ri();
    if(n < (m << 1)) return puts("0"), 0;
    if(D <= n - (m << 1)) return printf("%d\n", Pow(D, n)), 0;
    fac[0] = 1;
    for(int i = 1;i <= D; ++i)
        fac[i] = 1LL * fac[i - 1] * i % P;
    ivf[D] = Pow(fac[D], P - 2);
    for(int i = D; i; --i)
        ivf[i - 1] = 1LL * ivf[i] * i % P;
    for(int i = 0, w = 1;i <= D; ++i, w = -w)
        A[i] = 1LL * w * Pow(D - (i << 1), n) * ivf[i] % P, B[i] = ivf[i];
    Pre(D << 1);
    NTT(A); NTT(B);
    for(int i = 0;i < L; ++i)
        A[i] = 1LL * A[i] * B[i] % P;
    NTT(A);
    for(int i = 0;i <= D; ++i)
        f[i] = 1LL * A[L - i & L - 1] * InvL % P * C(D, i) % P * fac[i] % P * Pow(2, P - 1 - i) % P;
    for(int i = 0, w = 1;i <= D; ++i, w = -w)
        A[i] = 1LL * f[D - i] * fac[D - i] % P, B[i] = w * ivf[i];
    for(int i = D + 1; i < L; ++i)
        A[i] = B[i] = 0;
    NTT(A); NTT(B);
    for(int i = 0;i < L; ++i)
        A[i] = 1LL * A[i] * B[i] % P;
    NTT(A); long long ans = 0;
    for(int i = 0;i <= n - (m << 1); ++i) {
        int g = 1LL * A[L - (D - i) & L - 1] * InvL % P * ivf[i] % P;
        ans += g;
    }
    printf("%d\n", (ans % P + P) % P);
    return 0;
}

你可能感兴趣的:(数学相关-FFT与NTT,数学相关-生成函数)