[BZOJ5306][Haoi2018]染色(容斥+组合数学+NTT)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=5306

Solution

先回顾一下「 {1,2,...,M} { 1 , 2 , . . . , M } 个中恰好 K K 个合法」的容斥求法:

S{1,2,...,M}|S|K(1)|S|KC|S|K×({1,2,...,M}S)

接下来考虑一个问题:在 M M 种颜色中选出 i 种,每种颜色染 S S 个格子,剩下的 NiS 个格子分别染剩下的 Mi M − i 种颜色之一,求方案数。
(1)选出 i i 种颜色,方案数:
CMi

(2)每种颜色分别染 S S 格:
j=1iCN(j1)SS=N!(S!)i(NiS)!

(3)染剩下 NiS N − i S 格:

(Mi)NiS ( M − i ) N − i S

所以,方案数:
N!(S!)i(NiS)!CiM(Mi)NiS N ! ( S ! ) i ( N − i S ) ! C M i ( M − i ) N − i S

所以,答案为:
k=0MWki=kMN!(S!)i(NiS)!(1)ikCkiCiM(Mi)NiS ∑ k = 0 M W k ∑ i = k M N ! ( S ! ) i ( N − i S ) ! ( − 1 ) i − k C i k C M i ( M − i ) N − i S

复杂度显然是平方的。
但根据式 (mo) 子 (shu) ,容易想到把原式转化为卷积形式。
考虑把 Mi=k ∑ i = k M 后的式子改成枚举 i i 满足 i+k 为合法颜色数:
k=0MWki=0MkN!(S!)i+k(N(i+k)S)!(1)iCki+kCi+kM(Mik)N(i+k)S ∑ k = 0 M W k ∑ i = 0 M − k N ! ( S ! ) i + k ( N − ( i + k ) S ) ! ( − 1 ) i C i + k k C M i + k ( M − i − k ) N − ( i + k ) S

=N!k=0MWki=0Mk(1)i×(i+k)!i!k!×M!(i+k)!(Mik)!(Mik)N(i+k)S(S!)i+k(N(i+k)S)! = N ! ∑ k = 0 M W k ∑ i = 0 M − k ( − 1 ) i × ( i + k ) ! i ! k ! × M ! ( i + k ) ! ( M − i − k ) ! ( M − i − k ) N − ( i + k ) S ( S ! ) i + k ( N − ( i + k ) S ) !

=N!M!k=0MMkk!(S!)ki=0Mk(1)ii!(S!)i×(Mik)N(i+k)S(Mik)!(N(i+k)S)! = N ! M ! ∑ k = 0 M M k k ! ( S ! ) k ∑ i = 0 M − k ( − 1 ) i i ! ( S ! ) i × ( M − i − k ) N − ( i + k ) S ( M − i − k ) ! ( N − ( i + k ) S ) !

=N!M!k=0MMkk!(S!)ki=0Mk(1)ii!(S!)i×(Mki)N+(MkiM)S(Mki)!(N+(MkiM)S)! = N ! M ! ∑ k = 0 M M k k ! ( S ! ) k ∑ i = 0 M − k ( − 1 ) i i ! ( S ! ) i × ( M − k − i ) N + ( M − k − i − M ) S ( M − k − i ) ! ( N + ( M − k − i − M ) S ) !

设:
F(i)=(1)ii!(S!)i F ( i ) = ( − 1 ) i i ! ( S ! ) i

G(i)=iN+(iM)Si!(N+(iM)S)! G ( i ) = i N + ( i − M ) S i ! ( N + ( i − M ) S ) !

H(i)=Mii!(S!)i H ( i ) = M i i ! ( S ! ) i

那么就变成了:
N!M!k=0MH(k)(FG)(Mk) N ! M ! ∑ k = 0 M H ( k ) ( F ⨂ G ) ( M − k )

是一个卷积的形式,可以使用 NTT 计算出。
复杂度 O(n+mlogm) O ( n + m log ⁡ m )

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Step(i, a, b, x) for (i = a; i <= b; i += x)
#define Pow(k, n) for (k = 1; k < n; k <<= 1)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 3e5 + 5, M = 1e7 + 5, ZZQ = 1004535809;
int n, m, s, w[N], f[N], g[N], h[N], fac[M], inv[M],
spw[N], rev[N], ff = 1, gg, tot, gp[N], res[N], ans;
int qpow(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = 1ll * res * a % ZZQ;
        a = 1ll * a * a % ZZQ;
        b >>= 1;
    }
    return res;
}
void FFT(int n, int *a, int op) {
    int i, j, k, sp = n;
    gp[n] = qpow(op == 1 ? 3 : 334845270, (ZZQ - 1) / n);
    For (i, 0, n - 1) if (i < rev[i]) swap(a[i], a[rev[i]]);
    For (i, 1, tot) sp >>= 1,
        gp[sp] = 1ll * gp[sp << 1] * gp[sp << 1] % ZZQ;
    Pow(k, n) {
        int x = gp[k << 1];
        Step (i, 0, n - 1, k << 1) {
            int w = 1;
            For (j, 0, k - 1) {
                int u = a[i + j], v = 1ll * w * a[i + j + k] % ZZQ;
                a[i + j] = (u + v) % ZZQ;
                a[i + j + k] = (u - v + ZZQ) % ZZQ;
                w = 1ll * w * x % ZZQ;
            }
        }
    }
}
int main() {
    int i; fac[0] = inv[0] = inv[1] = spw[0] = 1;
    n = read(); m = read(); s = read();
    For (i, 0, m) w[i] = read();
    For (i, 1, max(n, m)) fac[i] = 1ll * fac[i - 1] * i % ZZQ;
    For (i, 2, max(n, m))
        inv[i] = 1ll * (ZZQ - ZZQ / i) * inv[ZZQ % i] % ZZQ;
    For (i, 2, max(n, m)) inv[i] = 1ll * inv[i] * inv[i - 1] % ZZQ;
    For (i, 1, m) spw[i] = 1ll * spw[i - 1] * inv[s] % ZZQ;
    For (i, 0, m) {
        f[i] = i & 1 ? ZZQ - 1 : 1;
        f[i] = 1ll * f[i] * inv[i] % ZZQ * spw[i] % ZZQ;
    }
    For (i, 0, m) {
        if (n + (i - m) * s < 0) continue;
        g[i] = qpow(i, n + (i - m) * s);
        g[i] = 1ll * g[i] * inv[i] % ZZQ * inv[n + (i - m) * s] % ZZQ;
    }
    For (i, 0, m) h[i] = 1ll * w[i] * inv[i] % ZZQ * spw[i] % ZZQ;
    while (ff <= (m << 1)) ff <<= 1, tot++;
    gg = qpow(ff, ZZQ - 2);
    For (i, 0, ff - 1)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << tot - 1);
    FFT(ff, f, 1); FFT(ff, g, 1);
    For (i, 0, ff - 1) res[i] = 1ll * f[i] * g[i] % ZZQ;
    FFT(ff, res, -1);
    For (i, 0, ff - 1) res[i] = 1ll * res[i] * gg % ZZQ;
    For (i, 0, m) ans = (ans + 1ll * h[i] * res[m - i] % ZZQ) % ZZQ;
    cout << 1ll * fac[m] * fac[n] % ZZQ * ans % ZZQ << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值