二项式反演小记

学习来源,orz

什么是反演?

类似这样 f n = i = 0 n A n , i g i f_n=\sum_{i=0}^n A_{n,i}* g_i ,现在已知 f f ,要你求 g g .这就是一个反演,即一个反推的过程.

假设 g n = i = 0 n B n , i f i g_n=\sum_{i=0}^n B_{n,i}*f_i .

这显然 A B = E A*B=E ,得到单位矩阵,也就是满足 f n = i = 0 n A n , i j = 0 n B i , j f i = i = 0 n [ A B ] n , j f i = i = 0 n [ i = n ] f i f_n=\sum_{i=0}^n A_{n,i}*\sum_{j=0}^n B_{i,j}*f_i=\sum_{i=0}^n [A*B]_{n,j}*f_i=\sum_{i=0}^n [i=n]*f_i .

即求和套反演为自身.

一些反演:莫比乌斯反演,二项式反演…(其实我就会这俩)

正题:二项式反演

二项式反演有三种形式: ( 0 n m ) (0\le n\le m)

  1. f n = i = 0 n ( 1 ) i ( n i ) g i g n = i = 0 n ( 1 ) i ( n i ) f i f_n=\sum_{i=0}^n (-1)^i \dbinom n i g_i\Leftrightarrow g_n=\sum_{i=0}^n (-1)^i \dbinom n if_i (极强的对称性)
  2. f n = i = 0 n ( n i ) g i g n = i = 0 n ( 1 ) n i ( n i ) f i f_n=\sum_{i=0}^n \dbinom n i g_i\Rightarrow g_n=\sum_{i=0}^n (-1)^{n-i} \dbinom n i f_i
  3. f n = i = n m ( i n ) g i g n = i = n m ( 1 ) n i ( i n ) f i f_n=\sum_{i=n}^m \dbinom i n g_i\Rightarrow g_n=\sum_{i=n}^m (-1)^{n-i} \dbinom i n f_i

下面证明第一条,其他的证明类似,读者自证不难.
f n = i = 0 n ( 1 ) i ( n i ) g i = i = 0 n ( 1 ) i ( n i ) j = 0 i ( 1 ) j ( i j ) f j f_n=\sum_{i=0}^n (-1)^i \dbinom n i g_i=\sum_{i=0}^n (-1)^i \dbinom n i \sum_{j=0}^i (-1)^j\dbinom i j f_j
= i = 0 n ( 1 ) i j = 0 i ( 1 ) j ( n i ) ( i j ) f j =\sum_{i=0}^n (-1)^i \sum_{j=0}^i (-1)^j\dbinom n i \dbinom i j f_j

由于:
( n i ) ( i j ) = n ! i ! ( n i ) ! i ! j ! ( i j ) ! = n ! ( n i ) ! 1 j ! ( i j ) ! = n ! ( n j ) ! j ! ( n j ) ! ( n i ) ! ( i j ) ! = ( n j ) ( n j n i ) \dbinom n i \dbinom i j=\dfrac{n!}{i!(n-i)!}*\dfrac{i!}{j!*(i-j)!}=\dfrac{n!}{(n-i)!}*\dfrac{1}{j!*(i-j)!}=\dfrac{n!}{(n-j)!j!}*\dfrac{(n-j)!}{(n-i)!*(i-j)!}=\dbinom n j \dbinom {n-j}{n-i}

0 0 = 1 ( ) 0^0=1(组合中的规定)

所以:
i = 0 n ( 1 ) i j = 0 i ( 1 ) j ( n i ) ( i j ) f j \sum_{i=0}^n (-1)^i \sum_{j=0}^i (-1)^j\dbinom n i \dbinom i j f_j
= i = 0 n ( 1 ) i j = 0 i ( 1 ) j ( n j ) ( n j n i ) f j =\sum_{i=0}^n (-1)^i \sum_{j=0}^i (-1)^j\dbinom n j \dbinom {n-j}{n-i}f_j
= j = 0 n ( 1 ) j f j ( n j ) i = j n ( 1 ) i ( n j n i ) =\sum_{j=0}^n (-1)^j f_j*\dbinom n j\sum_{i=j}^n(-1)^i*\dbinom{n-j}{n-i}
= j = 0 n ( 1 ) j f j ( n j ) i = 0 n j ( 1 ) n i ( n j i ) =\sum_{j=0}^n (-1)^j f_j *\dbinom n j*\sum_{i=0}^{n-j}(-1)^{n-i}*\dbinom{n-j}{i}
= j = 0 n ( 1 ) j + n f j ( n j ) i = 0 n j ( 1 ) i ( n j i ) =\sum_{j=0}^n (-1)^{j+n} f_j *\dbinom n j*\sum_{i=0}^{n-j}(-1)^{i}*\dbinom{n-j}{i}
= j = 0 n ( 1 ) j + n f j ( n j ) 0 n j ( ) =\sum_{j=0}^n (-1)^{j+n} f_j *\dbinom n j*0^{n-j}(二项式定理)
= j = 0 n [ j = n ] f j =\sum_{j=0}^n [j=n]f_j
证毕!

应用方法:
当有 1 -1 的话用第一条.
用第二三条的时候,即"至多"/"至少"量比"正好"量更好计算时.

应用题可以戳最上面的链接看.

补充资料

例题

1.BZOJ 4487: [Jsoi2015]染色问题

三维容斥-----对二项式定理的推广.

f i , j , k f_{i,j,k} 表示 i j k i行j列正好k种颜色 的方案数.
定义 g i , j , k g_{i,j,k} 表示 i , j , k 至多染i行,j列,k种颜色 的方案数.

则有:
{ g n , m , c = ( c + 1 ) n m g n , m , c = i = 0 n j = 0 m k = 0 c f i , j , k ( n i ) ( m j ) ( c k ) \begin{cases}g_{n,m,c}=(c+1)^{nm}\\g_{n,m,c}=\sum_{i=0}^n \sum_{j=0}^m \sum_{k=0}^c f_{i,j,k}*\dbinom n i \dbinom m j \dbinom c k\end{cases}
反演:
f n , m , c = i = 0 n j = 0 m k = 0 c ( 1 ) n i + m j + c k g i , j , k ( n i ) ( m j ) ( c k ) = i = 0 n j = 0 m k = 0 c ( 1 ) n i + m j + c k ( k + 1 ) i j ( n i ) ( m j ) ( c k ) f_{n,m,c}=\sum_{i=0}^n \sum_{j=0}^m \sum_{k=0}^c (-1)^{n-i+m-j+c-k}g_{i,j,k}*\dbinom n i \dbinom m j \dbinom c k=\sum_{i=0}^n \sum_{j=0}^m \sum_{k=0}^c (-1)^{n-i+m-j+c-k}(k+1)^{ij}*\dbinom n i \dbinom m j \dbinom c k
可理解为对每一维依次反演.

显然暴力需要: O ( n 3 log n ) O(n^3 \log n) 的复杂度.这样会T^T.
如果预处理幂的话也是 O ( n 3 ) O(n^3) 的,依然过不了.

观察式子可以发现有幂次和组合数,我们尝试用二项式定理进行合并.
( 1 ) m j [ ( k + 1 ) i ] j C m j = [ ( k + 1 ) i 1 ] j (-1)^{m-j}*[(k+1)^i]^j*C_m^j=[(k+1)^i-1]^j

预处理幂-> O ( n 2 ) O(n^2) .
计算复杂度-> O ( n 2 log n ) O(n^2 \log n) .

#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=410,size=1<<20,mod=1e9+7;

//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
	while(isdigit(c)) x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o> void qw(o x) {
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); puts("");
}

int n,m,c,p[N][N];
ll jc[N],inv[N],ans;

ll C(int x,int y) {return jc[x]*inv[y]%mod*inv[x-y]%mod;}

ll power(ll a,ll b=mod-2) {
	ll c=1;
	while(b) {
		if(b&1) c=c*a%mod;
		b=b/2; a=a*a%mod;
	}
	return c;
}

int main() {
	qr(n); qr(m); qr(c);
	jc[0]=inv[0]=1; for(int i=1;i<=400;i++) inv[i]=power(jc[i]=jc[i-1]*i%mod);
	for(int i=1;i<=c+1;i++)
		for(int j=p[i][0]=1;j<=m;j++)
			p[i][j]=(ll)p[i][j-1]*i%mod;
	for(int j=1,fj=(m&1)?1:-1;j<=m;j++,fj=-fj)
		for(int k=0,fk=((c&1)?-1:1)*fj;k<=c;k++,fk=-fk) 
			(ans += fk*C(m,j)*C(c,k)%mod*power(p[k+1][j]-1,n)%mod) %= mod;
	pr2((ans+mod)%mod);
	return 0;
}/*
二项式反演的三维形式.
g[i][j][k]表示至多i行,j列,k钟颜色的方案数.(k+1)^(i*j)

总复杂度O(n^2 log(n))
*/ 


2.CF1228E Another Filling the Grid

题意:给你个 n n n*n 的矩阵,每个格子可填 [ 1 , k ] [1,k] ,求每行每列的最小值为1的方案数.

可以发现 [ 2 , k ] [2,k] 的每个数是无区别的.

定义状态 f [ i ] [ j ] i j , f [ i ] [ j ] = ( k 1 ) n ( i + j ) i j k n 2 ( i + j ) n + i j f[i][j]表示至少i行j列不合法的情况数,f[i][j]=(k-1)^{n(i+j)-i*j}k^{n^2-(i+j)n+i*j} , g [ i ] [ j ] i j g[i][j]表示恰好i行j列不合法的情况数

f [ a ] [ b ] = i = a n j = 1 n C n i C n j g [ i ] [ j ] g [ a ] [ b ] = i = a n j = b ( 1 ) 2 n i j C i a C j b f [ i ] [ j ] f[a][b]=\sum_{i=a}^n \sum_{j=1}^n C_n^i C_n^j g[i][j]\Leftrightarrow g[a][b]=\sum_{i=a}^n\sum_{j=b}(-1)^{2n-i-j}C_i^a C_j^b f[i][j]

g [ 0 ] [ 0 ] g[0][0] 即为答案.

#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=255,size=1<<20,mod=1e9+7;

//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
	while(isdigit(c)) x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o> void qw(o x) {
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); puts("");
}

int n,m;
ll ans,C[N][N];

ll power(ll a,ll b) {
	ll c=1;
	while(b) {
		if(b&1) c=c*a%mod;
		b/=2; 	a=a*a%mod;
	}
	return c;
}

int main() {
	qr(n); qr(m);
	for(int i=0;i<=n;i++) C[i][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	for(int i=0;i<=n;i++)
		for(int j=0,k;j<=n;j++) {
			k=n*(i+j)-i*j;
			ans+=((i+j)&1?-1:1)*C[n][i]*C[n][j]%mod*power(m-1,k)%mod*power(m,n*n-k)%mod;
		}
	pr2((ans%mod+mod)%mod); 
	return 0;
}


3.Luogu P4859 已经没有什么好害怕的了

因为输入的两个数组( a , b a,b )的数互不相等,所以可得 ( a > b ) = ( n + k ) / 2 (a>b)的个数=(n+k)/2 (以下令 k = ( n + k ) / 2 k=(n+k)/2 ).

f [ i ] , g [ i ] f[i],g[i] 分别表示正好和至少为 i ( a > b ) i对(a>b) 的方案数.

我们设法构造 g > O ( n 2 ) D P g->O(n^2)DP

把两个数组都排序,定义 d p [ i ] [ j ] , p o s [ i ] i j , a [ i ] b dp[i][j],pos[i]分别表示前i个中选择j对,比a[i]小的b的数量 .
所以: f [ i ] [ j ] = f [ i 1 ] [ j ] + f [ i 1 ] [ j 1 ] m a x ( 0 , p o s [ i ] ( j 1 ) ) ( ) f[i][j]=f[i-1][j]+f[i-1][j-1]*max(0,pos[i]-(j-1))(不一定能凑对成功)

然后,反演即可.

#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=2010,size=1<<20,mod=1e9+9;

//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
	while(isdigit(c)) x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o> void qw(o x) {
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); puts("");
}

int n,m,a[N],b[N],f[N],jc[N],inv[N];

int main() {
	qr(n); qr(m); if((n+m)&1) puts("0"),exit(0); m=n+m>>1;
	jc[0]=inv[0]=inv[1]=1;
	for(int i=1;i<=n;i++) jc[i]=(ll)jc[i-1]*i%mod;
	for(int i=2;i<=n;i++) inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<=n;i++) inv[i]=(ll)inv[i-1]*inv[i]%mod;
	for(int i=1;i<=n;i++) qr(a[i]);
	for(int i=1;i<=n;i++) qr(b[i]);
	sort(a+1,a+n+1);
	sort(b+1,b+n+1);
	f[0]=1;
	for(int i=1,k=0;i<=n;i++) {
		while(k<n&&b[k+1]<a[i]) k++;
		for(int j=k;j;j--)
			f[j]=(f[j]+(ll)f[j-1]*max(0,k-(j-1)))%mod;
	}
	ll ans=0;
	for(int i=m,F=1;i<=n;i++) {
		f[i]=(ll)f[i]*jc[n-i]%mod;
		ans+=(ll)F*jc[i]%mod*inv[i-m]%mod*f[i]%mod;
		F=-F;
	}
	ans=(ans%mod+mod)%mod;
	ans=ans*inv[m]%mod;
	pr2(ans);
	return 0;
}


4.BZOJ 2839 集合计数

一个有 N N 个元素的集合有 2 N 2^N 个不同子集(包含空集),现在要在这 2 N 2^N 个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 K K ,求取法的方案数,答案模1000000007。

第一次自己做出一道二项式反演的题,好激动啊

显然这是"至少"问题,我们直接确定并集的最小大小其他的暴力枚举即可.
f [ i ] , g [ i ] f[i],g[i] 为正好为 i i 和至少为 i i 的情况数.

g [ a ] = i = a n ( i a ) f [ i ] = ( n a ) ( 2 2 n a 1 ) g[a]=\sum_{i=a}^n \dbinom i a f[i]=\dbinom n a (2^{2^{n-a}}-1)
f [ k ] = i = k n ( 1 ) i k ( i k ) g [ i ] = i = k n ( 1 ) i k ( i k ) ( n i ) ( 2 2 n i 1 ) f[k]=\sum_{i=k}^n (-1)^{i-k} \dbinom i k g[i]=\sum_{i=k}^n (-1)^{i-k} \dbinom i k \dbinom n i (2^{2^{n-i}}-1)

如何理解第一个式子呢?
我们钦定 a a 个元素的方案数显然为 ( n a ) \dbinom n a ,然后,剩余 n a n-a 个位置,形成了 2 n a 2^{n-a} 个子集,这些子集要么选要么不选,即 2 2 n a 2^{2^{n-a}} 种方案(注意,先算上部),同时要不为空集,所以-1即可.

暴力 O ( n log 2 m o d    ) O(n\log^2\mod) 并没有被卡.

int n,m;
ll jc[N],inv[N],ans;

ll power(ll a,ll b=mod-2,ll p=mod) {
	ll c=1;
	while(b) {
		if(b&1) c=c*a%p;
		b/=2;	a=a*a%p;
	}
	return c;
}

ll C(int x,int y) {return jc[x]*inv[y]%mod*inv[x-y]%mod;}

int main() {
	qr(n);qr(m);
	
	jc[0]=1;for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod;
	inv[n]=power(jc[n]);for(int i=n;i;i--) inv[i-1]=inv[i]*i%mod;
	
	for(int i=m,f=1;i<=n;i++)
		ans+=f*C(i,m)*C(n,i)%mod*(power(2,power(2,n-i,mod-1))-1)%mod,f=-f;
	pr2((ans%mod+mod)%mod);
	return 0;
}


闲来无事,不妨卡卡常数.

我们显然可以预处理2的幂来加速.
对于指数部分 % ( m o d 1 ) \% (mod-1) (费马小定理).
下面 % m o d \% mod .

然后,我们直接值域分块,可以用 O ( m o d ) O(\sqrt mod) 完成预处理.

复杂度: O ( n + m o d ) O(n+\sqrt mod)

质的飞越:
在这里插入图片描述

int n,m;
ll jc[N],inv[N],ans,p1[M+5],p2[M+5],pw[N];

ll power(ll a,ll b=mod-2,ll p=mod) {
	ll c=1;
	while(b) {
		if(b&1) c=c*a%p;
		b/=2;	a=a*a%p;
	}
	return c;
}

ll P1(int x) {return p1[x>>15]*p2[x&(M-1)]%mod;}


ll C(int x,int y) {return jc[x]*inv[y]%mod*inv[x-y]%mod;}

int main() {
	qr(n);qr(m);
	
	jc[0]=1;for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod;
	inv[n]=power(jc[n]);for(int i=n;i;i--) inv[i-1]=inv[i]*i%mod;
	
	p2[0]=1;for(int i=1;i<=M;i++)p2[i]=p2[i-1]*2%mod;
	p1[0]=1;p1[1]=p2[M]%mod;for(int i=2;i<M;i++) p1[i]=p1[i-1]*p1[1]%mod;
	
	pw[0]=1;for(int i=1;i<=n;i++) pw[i]=pw[i-1]*2%(mod-1);
	
	for(int i=m,f=1;i<=n;i++)
		ans+=f*C(i,m)*C(n,i)%mod*(P1(pw[n-i])-1)%mod,f=-f;
	pr2((ans%mod+mod)%mod);
	return 0;
}


我**了,可以发现 2 2 i = ( 2 2 i 1 ) 2 2^{2^i}=(2^{2^{i-1}})^2 ,所以我们倒着推即可.

5.BZOJ 4710: [Jsoi2011]分特产

参考

#include<cstdio>
using namespace std;
typedef long long ll;
const int N=2010,mod=1e9+7;

int n,m,a[N];
ll ans,c[N][N],f; 
void qr(int &x) {scanf("%d",&x);}

int main() {
	qr(n); qr(m);
	for(int i=0;i<=2000;i++) {
		c[i][0]=1;
		for(int j=1;j<=i;j++)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	}
	for(int i=1;i<=m;i++) qr(a[i]);
	for(int i=0,F=1,f;i<=n;i++) {
		f=c[n][i];
		for(int j=1;j<=m;j++)
			f=f*c[n-i+a[j]-1][a[j]]%mod;
		(ans+=F*f+mod)%=mod; 
		F=-F; 
	}
	printf("%lld\n",ans);return 0;
}
 

6.CF285E Positions in Permutations

题意:给你一个长度为 n n 的排列 p p m m ,求 f ( p ) = i = 1 n [ p i i = 1 ] = m f(p)=\sum_{i=1}^n[|p_i-i|=1]=m p p 的个数.

f ( i ) f(i) 为恰好为 i i 的方案数, g ( i ) g(i) 是至少为 i i 的方案数.

则有:
{ g ( a ) = i = a n ( i m ) f ( i ) f ( a ) = i = a n ( 1 ) i a ( i a ) g ( i ) \begin{cases}g(a)=\sum_{i=a}^n \dbinom i m f(i)\\f(a)=\sum_{i=a}^n (-1)^{i-a}\dbinom i a g(i)\end{cases}

比较难的地方是求 g g .

设计一个DP,定义 d p [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] i j , i , i + 1 dp[i][j][0/1][0/1]表示前i个数中有j个满足条件的数,三四维表示i,i+1是否被选择 ,乱搞即可.

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long 
using namespace std;
const int N=2010,mod=1e9+7;

int n,m;
ll jc[N],inv[N],f[N][N][4],ans;
//f[i][j][k]表示前i个数中有j个好数,k的两位分别表示i,i+1位置是否取. 
ll C(int x,int y) {return jc[x]*inv[y]%mod*inv[x-y]%mod;}

int main() {
	scanf("%d %d",&n,&m);
	jc[0]=inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
	for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod,inv[i]=inv[i]*inv[i-1]%mod;
	f[1][1][1]=f[1][0][0]=1;
	for(int i=2;i<=n;i++) {
		f[i][0][0]=1;
		for(int j=1;j<=i;j++) 
			f[i][j][0]=(f[i-1][j-1][0]+f[i-1][j][0]+f[i-1][j][2])%mod,
			f[i][j][2]=(f[i-1][j-1][1]+f[i-1][j][1]+f[i-1][j][3])%mod,
			f[i][j][1]=(f[i-1][j-1][0]+f[i-1][j-1][2])%mod,
			f[i][j][3]=(f[i-1][j-1][1]+f[i-1][j-1][3])%mod;
	}
	ll g; 
	for(int i=m,F=1;i<=n;i++) {
		g=F*(f[n][i][0]+f[n][i][2])*jc[n-i]%mod*C(i,m)%mod+mod;
		(ans += g) %= mod; F=-F;
	}
	printf("%lld\n",ans); return 0;
}

7.Luogu P4491 [HAOI2018]染色

为了报答小 C C 的苹果, 小 G G 打算送给热爱美术的小 C C 一块画布, 这块画布可 以抽象为一个长度为 N N 的序列, 每个位置都可以被染成 M M 种颜色中的某一种.

然而小 C C 只关心序列的 N N 个位置中出现次数恰好为 S S 的颜色种数, 如果恰 好出现了 S S 次的颜色有 K K 种, 则小 C 会产生 W k W_k 的愉悦度.

C C 希望知道对于所有可能的染色方案, 他能获得的愉悦度的和对 1004535809 1004535809 取模的结果是多少.

N 1 0 7 , M 1 0 5 N\le 10^7,M\le 10^5 .

参考

#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e7+10,M=1<<18|10,size=1<<20,mod=1004535809,g=3,G=334845270;

//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
	while(isdigit(c)) x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o> void qw(o x) {
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); puts("");
}

int n,lim,m,s,jc[N],inv[N],a[M],b[M],tr[M],ans,w[M];

ll power(ll a,ll b=mod-2) {
	ll c=1;
	while(b) {
		if(b&1) c=c*a%mod;
		b /= 2; a=a*a%mod;
	}
	return c;
}

void add(int &x,int y) {x+=y; if(x>=mod) x-=mod;}
void del(int &x,int y) {x-=y; if(x<0) x+=mod;}

void NTT(int *f,int e) {
	for(int i=1;i<n;i++) 
		if(i<tr[i]) swap(f[i],f[tr[i]]);
	for(int p=2,len=1;p<=n;len=p,p*=2) {
		ll tag=power(e,(mod-1)/p);
		for(int k=0;k<n;k+=p) {
			ll buf=1,t;
			for(int l=k;l<k+len;l++) {
				t=f[l+len]*buf%mod;
				f[l+len]=(f[l]-t+mod)%mod;
				add(f[l],t);
				buf=buf*tag%mod;
			}
		}
	}
}

ll C(int x,int y) {return (ll)jc[x]*inv[y]%mod*inv[x-y]%mod;}

int main() {
	qr(n); qr(m); qr(s); 
	for(int i=0;i<=m;i++) qr(w[i]);
	lim=max(n,m);
	jc[0]=1;for(int i=1;i<=lim;i++) jc[i]=(ll)jc[i-1]*i%mod;
	inv[lim]=power(jc[lim]);for(int i=lim; i;i--) inv[i-1]=(ll)inv[i]*i%mod;
	lim=min(m,n/s);
	for(int i=0;i<=lim;i++) b[i]=(i&1?-inv[i]+mod:inv[i]);
	ll t=1,T=inv[s];
	for(int i=0;i<=lim;i++) a[lim-i]=jc[i]*C(m,i)%mod*jc[n]%mod*inv[n-i*s]%mod*t%mod*power(m-i,n-i*s)%mod,t=t*T%mod;
	
	for(n=1;n<=lim;n<<=1);
	n*=2;for(int i=1;i<n;i++) tr[i]=(tr[i>>1]>>1)|(i&1?n>>1:0);
	NTT(a,g); NTT(b,g); for(int i=0;i<n;i++) a[i]=(ll)a[i]*b[i]%mod;
	NTT(a,G); ll Inv=power(n); for(int i=0;i<=lim;i++) add(ans,a[lim-i]*Inv%mod*inv[i]%mod*w[i]%mod);
	pr2(ans);
	return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_42886072/article/details/105834578