[CTS2019]珍珠

时间:2021-08-12
本文章向大家介绍[CTS2019]珍珠,主要包括[CTS2019]珍珠使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

link

难度

思维难度 : 四星

代码难度 : 三星

标签

反演 + 计数 + 数学 + 生成函数 + \(fft\)

题解

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

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

\[\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 \]

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

用二项式反演, 设 \(f_i\) 表示恰好有 \(i\) 种颜色的珍珠有奇数个的方案数,\(g_i\) 表示钦定有 \(i\) 种颜色的珍珠有奇数个的方案数.

\[f_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 \]

发现这是一个减法卷积,得到 \(g_i\) 后就可以求出 \(f_i\) 了.
做减法卷积,如 \(h_k = \sum_{i=k}^{n}f_ig_{i-k}\) , 把 \(g_{i-k}\) 变成 \(g_{n - (i - k)}\) 就可以做加法卷积了. 相应的,原来的 \(h_k\)\(h_{n+k}\)

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

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

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

于是有:

\[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

#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. \(e^x\) 全是 \(1\)\(e^{-x}\) 奇数位是 \(-1\) , 偶数位是 \(1\)\(\frac{e^x - e^{-x}}{2}\) , 奇数位是 \(1\) , 偶数位是 \(0\)

原文地址:https://www.cnblogs.com/youwike/p/15132018.html