[HAOI2018] 染色(二项式反演+NTT)

洛谷链接

显然颜色数量不会超过 lim ⁡ = min ⁡ ( m , n s ) \lim=\min(m,\frac ns) lim=min(m,sn)

f i : f_i: fi: 至少有 i i i 种颜色恰好出现了 s s s 次的方案数。

f i = ( m i ) ⋅ n ! ( s ! ) i ( n − i s ) ! ⋅ ( m − i ) n − i s f_i=\binom mi·\frac{n!}{(s!)^i(n-is)!}·(m-i)^{n-is} fi=(im)(s!)i(nis)!n!(mi)nis

m m m 种颜色中选 i i i 种, ( m i ) \binom mi (im)

n n n 个位置分成 i + 1 i+1 i+1 个部分,钦定的 i i i 种颜色每种出现 s s s 次,剩下 m − i m-i mi 种颜色共 n − i s n-is nis 个。

先当作是可重的全排列,即 n ! s ! s ! . . . s ! ⏟ i ( n − i s ) ! \frac{n!}{\underbrace{s!s!...s!}_i(n-is)!} i s!s!...s!(nis)!n!

i i i 各部分都是只有一种颜色,后面部分每个有 m − i m−i mi 种取法,所以要乘上 ( m − i ) n − i s (m-i)^{n-is} (mi)nis

a n s i : ans_i: ansi: 恰有 i i i 个颜色出现恰好 s s s 次的方案数。

直接二项式反演: a n s i = ∑ j = i lim ⁡ ( − 1 ) j − i ( j i ) f j ans_i=\sum_{j=i}^{\lim}(-1)^{j-i}\binom jif_j ansi=j=ilim(1)ji(ij)fj
a n s i = ∑ j = i lim ⁡ ( − 1 ) j − i ( j i ) f j = ∑ j = i lim ⁡ ( − 1 ) j − 1 j ! i ! ( j − i ) ! f j ⇒ a n s i ⋅ i ! = ∑ j = i lim ⁡ ( − 1 ) j − i ( j − i ) ! j ! ⋅ f j ans_i=\sum_{j=i}^{\lim}(-1)^{j-i}\binom jif_j=\sum_{j=i}^{\lim}(-1)^{j-1}\frac{j!}{i!(j-i)!}f_j\Rightarrow ans_i·i!=\sum_{j=i}^{\lim}\frac{(-1)^{j-i}}{(j-i)!}j!·f_j ansi=j=ilim(1)ji(ij)fj=j=ilim(1)j1i!(ji)!j!fjansii!=j=ilim(ji)!(1)jij!fj
F i = i ! ⋅ f i , G i = ( − 1 ) i i ! F_i=i!·f_i,G_i=\frac{(-1)^i}{i!} Fi=i!fi,Gi=i!(1)i,则 a n s i = 1 i ! ∑ j = i lim ⁡ G j − i F j = 1 i ! ∑ j = 0 lim ⁡ − i G j F j + i ans_i=\frac{1}{i!}\sum_{j=i}^{\lim} G_{j-i}F_j=\frac{1}{i!}\sum_{j=0}^{\lim-i}G_jF_{j+i} ansi=i!1j=ilimGjiFj=i!1j=0limiGjFj+i

反转 F F F,即 F i = ( lim ⁡ − i ) ! ⋅ f lim ⁡ − i F_i=(\lim-i)!·f_{\lim-i} Fi=(limi)!flimi,则 a n s i = 1 i ! ∑ j = 0 lim ⁡ − i G j F lim ⁡ − i − j ans_i=\frac{1}{i!}\sum_{j=0}^{\lim-i}G_jF_{\lim-i-j} ansi=i!1j=0limiGjFlimij

可以卷了 G ∗ F G*F GF NTT \text{NTT} NTT 做即可。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mod 1004535809
#define maxn 10000005
#define maxm 500000
int fac[maxn], inv[maxn], r[maxm], W[maxm], f[maxm], g[maxm];
int len;

int qkpow( int x, int y ) {
    int ans = 1;
    while( y ) {
        if( y & 1 ) ans = ans * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return ans;
}

void NTT( int *c, int op ) {
    for( int i = 0;i < len;i ++ ) if( i < r[i] ) swap( c[i], c[r[i]] );
    for( int i = 1;i < len;i <<= 1 ) {
        int omega = qkpow( op == 1 ? 3 : mod / 3 + 1, (mod - 1) / (i << 1) );
        for( int j = 0;j < len;j += (i << 1) )
            for( int k = 0, w = 1;k < i;k ++, w = w * omega % mod ) {
                int x = c[j + k], y = c[j + k + i] * w % mod;
                c[j + k] = ( x + y ) % mod;
                c[j + k + i] = ( x - y + mod ) % mod;
            }
    }
    if( op == -1 ) {
        int inv = qkpow( len, mod - 2 );
        for( int i = 0;i < len;i ++ ) c[i] = c[i] * inv % mod;
    }
}

void init( int n ) {
    fac[0] = inv[0] = 1;
    for( int i = 1;i <= n;i ++ ) fac[i] = fac[i - 1] * i % mod;
    inv[n] = qkpow( fac[n], mod - 2 );
    for( int i = n - 1;i;i -- ) inv[i] = inv[i + 1] * ( i + 1 ) % mod;
}

int C( int n, int m ) { return fac[n] * inv[m] % mod * inv[n - m] % mod; }

signed main() {
    int n, m, s;
    scanf( "%lld %lld %lld", &n, &m, &s );
    for( int i = 0;i <= m;i ++ ) scanf( "%lld", &W[i] );
    init( max( n, m ) );
    int lim = min( m, n / s );
    for( int i = 0;i <= lim;i ++ ) {
        f[i] = fac[i] * C(m, i) % mod * fac[n] % mod * qkpow(inv[s], i) % mod * inv[n - i * s] % mod * qkpow(m - i, n - i * s) % mod;
        g[i] = (i & 1) ? mod - inv[i] : inv[i];
    }
    reverse( f, f + lim + 1 );
    int l;
    for( len = 1, l = 0;len <= (lim << 1);len <<= 1, l ++ );
    for( int i = 0;i < len;i ++ ) r[i] = (r[i >> 1] >> 1) | ((i & 1) << l - 1 );
    NTT( f, 1 ), NTT( g, 1 );
    for( int i = 0;i < len;i ++ ) f[i] = f[i] * g[i] % mod;
    NTT( f, -1 );
    int ans = 0;
    for( int i = 0;i <= lim;i ++ ) ( ans += W[i] * inv[i] % mod * f[lim - i] ) %= mod;
    printf( "%lld\n", ans );
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值