二项式反演小记

学习来源,orz

什么是反演?

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

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

这显然 A ∗ B = E A*B=E AB=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 fn=i=0nAn,ij=0nBi,jfi=i=0n[AB]n,jfi=i=0n[i=n]fi.

即求和套反演为自身.

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

正题:二项式反演

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

  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 fn=i=0n(1)i(in)gign=i=0n(1)i(in)fi(极强的对称性)
  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 fn=i=0n(in)gign=i=0n(1)ni(in)fi
  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 fn=i=nm(ni)gign=i=nm(1)ni(ni)fi

下面证明第一条,其他的证明类似,读者自证不难.
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 fn=i=0n(1)i(in)gi=i=0n(1)i(in)j=0i(1)j(ji)fj
= ∑ 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=0n(1)ij=0i(1)j(in)(ji)fj

由于:
( 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} (in)(ji)=i!(ni)!n!j!(ij)!i!=(ni)!n!j!(ij)!1=(nj)!j!n!(ni)!(ij)!(nj)!=(jn)(ninj)

0 0 = 1 ( 组 合 中 的 规 定 ) 0^0=1(组合中的规定) 00=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=0n(1)ij=0i(1)j(in)(ji)fj
= ∑ 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 =i=0n(1)ij=0i(1)j(jn)(ninj)fj
= ∑ 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=0n(1)jfj(jn)i=jn(1)i(ninj)
= ∑ 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=0n(1)jfj(jn)i=0nj(1)ni(inj)
= ∑ 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=0n(1)j+nfj(jn)i=0nj(1)i(inj)
= ∑ 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=0n(1)j+nfj(jn)0nj()
= ∑ j = 0 n [ j = n ] f j =\sum_{j=0}^n [j=n]f_j =j=0n[j=n]fj
证毕!

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

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

补充资料

例题

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

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

f i , j , k f_{i,j,k} fi,j,k表示 i 行 j 列 正 好 k 种 颜 色 i行j列正好k种颜色 ijk的方案数.
定义 g i , j , k g_{i,j,k} gi,j,k表示 至 多 染 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} gn,m,c=(c+1)nmgn,m,c=i=0nj=0mk=0cfi,j,k(in)(jm)(kc)
反演:
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 fn,m,c=i=0nj=0mk=0c(1)ni+mj+ckgi,j,k(in)(jm)(kc)=i=0nj=0mk=0c(1)ni+mj+ck(k+1)ij(in)(jm)(kc)
可理解为对每一维依次反演.

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

观察式子可以发现有幂次和组合数,我们尝试用二项式定理进行合并.
( − 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 (1)mj[(k+1)i]jCmj=[(k+1)i1]j

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

#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 nn的矩阵,每个格子可填 [ 1 , k ] [1,k] [1,k],求每行每列的最小值为1的方案数.

可以发现 [ 2 , k ] [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} f[i][j]ij,f[i][j]=(k1)n(i+j)ijkn2(i+j)n+ij, g [ i ] [ j ] 表 示 恰 好 i 行 j 列 不 合 法 的 情 况 数 g[i][j]表示恰好i行j列不合法的情况数 g[i][j]ij

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] f[a][b]=i=anj=1nCniCnjg[i][j]g[a][b]=i=anj=b(1)2nijCiaCjbf[i][j]

g [ 0 ] [ 0 ] 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)的数互不相等,所以可得 ( a > b ) 的 个 数 = ( n + k ) / 2 (a>b)的个数=(n+k)/2 (a>b)=(n+k)/2(以下令 k = ( n + k ) / 2 k=(n+k)/2 k=(n+k)/2).

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

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

把两个数组都排序,定义 d p [ i ] [ j ] , p o s [ i ] 分 别 表 示 前 i 个 中 选 择 j 对 , 比 a [ i ] 小 的 b 的 数 量 dp[i][j],pos[i]分别表示前i个中选择j对,比a[i]小的b的数量 dp[i][j],pos[i]ij,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))(不一定能凑对成功) f[i][j]=f[i1][j]+f[i1][j1]max(0,pos[i](j1))()

然后,反演即可.

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

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

显然这是"至少"问题,我们直接确定并集的最小大小其他的暴力枚举即可.
f [ i ] , g [ i ] f[i],g[i] f[i],g[i]为正好为 i 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) g[a]=i=an(ai)f[i]=(an)(22na1)
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) f[k]=i=kn(1)ik(ki)g[i]=i=kn(1)ik(ki)(in)(22ni1)

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

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

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) %(mod1)(费马小定理).
下面 % m o d \% mod %mod.

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

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

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

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 22i=(22i1)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 n的排列 p p p m m m,求 f ( p ) = ∑ i = 1 n [ ∣ p i − i ∣ = 1 ] = m f(p)=\sum_{i=1}^n[|p_i-i|=1]=m f(p)=i=1n[pii=1]=m p p p的个数.

f ( i ) f(i) f(i)为恰好为 i i i的方案数, g ( i ) g(i) g(i)是至少为 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(a)=i=an(mi)f(i)f(a)=i=an(1)ia(ai)g(i)

比较难的地方是求 g 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是否被选择 dp[i][j][0/1][0/1]ij,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 C 的苹果, 小 G G G 打算送给热爱美术的小 C C C 一块画布, 这块画布可 以抽象为一个长度为 N N N 的序列, 每个位置都可以被染成 M M M 种颜色中的某一种.

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

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

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

参考

#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;
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值