二项式反演

二项式反演

二项式反演 - 形式1

[f(n)=sumlimits_{i=0}^ndbinom n ig(i)Leftrightarrow g(n)=sumlimits_{i=0}^n(-1)^{n-i}dbinom n if(i) ]

二项式反演 - 形式2

[f(k)=sumlimits_{i=k}^ndbinom i kg(i)Leftrightarrow g(k)=sumlimits_{i=k}^n(-1)^{i-k}dbinom i kf(i) ]

证明1 - 容斥原理

熟知容斥原理:

[f_geqslant(T)=sumlimits_{Ysupseteq T}f_=(Y)Leftrightarrow f_=(T)=sumlimits_{Ysupseteq T}(-1)^{|Y-T|}f_geqslant(Y) ]

(f_geqslant(T)=f(i),f_=(T)=g(i)),即得

证毕

证明2 - 代数法

首先我们证明形式1

[egin{align*} f(n)&=sumlimits_{i=0}^ndbinom n ig(i) \ &=sumlimits_{i=0}^ndbinom n isumlimits_{j=0}^i(-1)^{i-j}dbinom i jf(j) \ &=sumlimits_{j=0}^ndbinom n jf(j)sumlimits_{i=j}^n(-1)^{i-j}dbinom{n-j}{i-j} \ &=sumlimits_{j=0}^ndbinom n jf(j)sumlimits_{i=0}^n(-1)^{i}dbinom{n-j}{i} \ &=f(n) end{align*} ]

证毕

然后我们来证明形式二

[egin{align*} f(k)&=sumlimits_{i=k}^ndbinom i kg(i) \ &=sumlimits_{i=k}^ndbinom i ksumlimits_{j=i}^n(-1)^{j-i}dbinom j if(j) \ &=sumlimits_{j=k}^nsumlimits_{i=k}^jdbinom j kdbinom{j-k}{i-k}(-1)^{j-i}f(j) \ &=sumlimits_{j=k}^ndbinom j kf(j)sumlimits_{i=0}^jdbinom{j-k}{i}(-1)^{j-i-k} \ &=f(k) end{align*} ]

证毕

相关应用

有了二项式反演,我们就可以很方便而又清晰的讨论一些容斥式子

奇奇怪怪的容斥限制就基本不会影响你分析题目

BZOJ2839 集合计数

题目分析

要求选定集合的交集恰好为 (k),并不是很好考虑,但是我们可以考虑至少为 (k) 的情况

不难想到,只要我们能保证我们选出来的集合的交集中有我们知道的某 (k) 个数,那么剩下的怎么选都是合法的

于是我们强制选定 (k) 个元素,即为交集必定会有的那 (k) 个,方法数 (dbinom n k)

然后剩下的数的幂集中随便选,即选择一个子集,方法数 (2^{2^{n-i}}-1)

我们设一个函数 (g(i)) 来表示这个,即 (g(i)=dbinom n k(2^{2^{n-i}}-1))

然后我们就可以考虑正好为 (k) 个时的情况的方案数 (f(i))

不难发现 (g(k)) 其实就是 (ige k) 时,(f(i)) 的选取方式加起来,并且 (i) 在上指标的位置,即 (g(k)=sumlimits_{i=k}^ndbinom i kf(i))

这显然是一个二项式反演的形式,大力反演即得 (f(k)=sumlimits_{i=k}^n(-1)^{i-k}dbinom{n}{k}dbinom{n-k}{i-k}(2^{2^{n-i}}-1))(二项式系数做了一个小小的变换)

注意事项

如果您最后的乘法写成了 (f(k)=sumlimits_{i=k}^n(-1)^{i-k}dbinom{n}{k}dbinom{n-k}{i-k}2^{2^{n-i}}) 当然在bzoj上也是能A掉的,不过您可以试试这组数据:
in: 768 768
out: 1
wrong answer: 2

这是因为选择空集和全部不选效果是一样的

代码实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 1000001
#define M 5001
#define INF 1100000000
#define Kafuu return
#define Chino 0
#define fx(l,n) inline l n
#define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
#define cpy(f,t,ty,len) memcpy(t,f,sizeof(ty)*len)
#define int long long
#define R register
#define C const
using namespace std;
C int mod=1e9+7;
int n,k,ans,bf,texp[N],fac[N];
fx(int,gi)(){
	R char c=getchar();R int s=0,f=1;
	while(c>'9'||c<'0'){
		if(c=='-') f=-f;
		c=getchar();
	}
	while(c<='9'&&c>='0') s=(s<<3)+(s<<1)+(c-'0'),c=getchar();
	return s*f;
}
fx(int,fpow)(int a,int b=mod-2){
	int sum=1;
	while(b){
		if(b&1) (sum*=a)%=mod;
		(a*=a)%=mod;
		b>>=1;
	}
	return sum;
}
fx(int,binom)(int up,int down){
	return fac[up]*fpow(fac[down])%mod*fpow(fac[up-down])%mod;
}
signed main(){
	n=gi(),k=gi();
	texp[0]=2;fac[0]=1;
	for(R int i=1;i<=n;i++){
		texp[i]=texp[i-1]*texp[i-1]%mod;
		fac[i]=fac[i-1]*i%mod;
	}
	for(R int i=k;i<=n;i++){
		bf=0;
		bf+=(texp[n-i]-1)*binom(n,k)%mod*binom(n-k,i-k);
		if((i-k)%2) bf=-bf;
		(ans+=bf+mod)%=mod;
	}
	printf("%lld",ans);
}

P5505 [JSOI2011]分特产

题目分析

每个人至少分得一个特产不就是恰好每个人都分得特产么

老套路,设 (f(i)) 为至少 (i) 个人没有特产,(g(i)) 为恰好 (i) 个人没有特产

我们对每个特产顺次分配,发现这个过程就是解一个方程:

[x_1+x_2+cdots+x_{n-k}=n_i ]

其中 (n_i) 为第 (i) 种特产的数量,我们要求的即为这个方程的非负整数解的个数

立即得:(f(k)=dbinom n kprodlimits_{i=1}^mdbinom{n_i+n-k-1}{n+k-1})

然后我们和 (g) 联立一下,(f(i)=sumlimits_{j=i}^ndbinom j if(j))

大力二项式反演:(g(0)=sumlimits_{i=0}^n(-1)^if(i)=sumlimits_{i=0}^n(-1)^idbinom n iprodlimits_{j=1}^mdbinom{n_j+n-i-1}{n+i-1})

直接做就可以了,后面是可以预处理的,但是处不处理都能过

代码实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 1000001
#define M 5001
#define INF 1100000000
#define Kafuu return
#define Chino 0
#define fx(l,n) inline l n
#define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
#define cpy(f,t,ty,len) memcpy(t,f,sizeof(ty)*len)
#define int long long
#define R register
#define C const
using namespace std;
C int mod=1e9+7;
int n,m,v[N],ans,bf,fac[N];
fx(int,gi)(){
	R char c=getchar();R int s=0,f=1;
	while(c>'9'||c<'0'){
		if(c=='-') f=-f;
		c=getchar();
	}
	while(c<='9'&&c>='0') s=(s<<3)+(s<<1)+(c-'0'),c=getchar();
	return s*f;
}
fx(int,fpow)(int a,int b=mod-2){
	int sum=1;
	while(b){
		if(b&1) (sum*=a)%=mod;
		(a*=a)%=mod;
		b>>=1;
	}
	return sum;
}
fx(int,binom)(int up,int dn){
	return fac[up]*fpow(fac[up-dn])%mod*fpow(fac[dn])%mod;
}
signed main(){
	n=gi(),m=gi();
	for(R int i=1;i<=m;i++) v[i]=gi();
	fac[0]=1;
	for(R int i=1;i<=100000;i++) fac[i]=fac[i-1]*i%mod;
	for(R int i=0;i<=n;i++){
		bf=1;
		for(R int o=1;o<=m;o++) (bf*=binom(v[o]+n-i-1,n-i-1))%=mod;
		(bf*=binom(n,i))%=mod;
		if(i%2) bf=-bf;
		(ans+=mod+bf)%=mod;
	}
	printf("%lld",ans);
}

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

我害怕儒略日,还害怕你出毒瘤题

题目分析

由之前的套路,我们只需要求出一个 (f(i)),表示 (n) 个数中糖果比药片能量大的组数至少为 (i) 的方案数

而在这道题目中,(f(i)) 并不可用常规的组合式子表示出来

我们发现如果知道前 (n-1) 组的 (f(i),f(i-1)) 函数值就能很方便表示 (n) 组时的 (f(i))

计算方式跟动态规划一样,于是我们设 (f_{i,j}),表示前 (i) 组至少有 (j) 组被配对了的方案数

显然有 (f_{i,j}=f_{i-1,j}+xf_{i-1,j-1})

(x) 怎么表示出来呢,不难想到如果这一组被配对成功了,那么在药片组里选择的一定是能量比其小的

我们并不知道,即使知道了也没有记录,即使记录了也不能很好处理前 (j-1) 组中被选择的

似乎陷入了困境

但是题目中求的只是方案数,也就是说不管你动态规划怎么循环,正序倒序欧拉序dfs序都是可以的

于是将其进行排序

如果从大至小排序,那么依然不好统计,我们选择将两个数组从小至大排序

这样,设 (Y_i) 表示 (B) 中小于 (A_i) 的元素的个数,立即可得

[f_{i,j}=f_{i-1,j}+(Y_i-(j-1))f_{i-1,j-1} ]

然后求 (f(i))(g(i)) 的关系式:

[(n-m)!f_{n,m}=sumlimits_{i=m}^ndbinom i mg(i) ]

这是个裸的二项式反演,答案即为

[g(m)=sumlimits_{i=m}^n(-1)^{i-m}dbinom i m(n-i)!f_{n,i} ]

代码实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 1000001
#define M 3001
#define INF 1100000000
#define Kafuu return
#define Chino 0
#define fx(l,n) inline l n
#define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
#define cpy(f,t,ty,len) memcpy(t,f,sizeof(ty)*len)
#define int long long
#define R register
#define C const
using namespace std;
C int mod=1e9+9;
int n,k,A[N],B[N],ans,dp[M][M],les[N],m,fac[N],bf;
fx(int,gi)(){
	R char c=getchar();R int s=0,f=1;
	while(c>'9'||c<'0'){
		if(c=='-') f=-f;
		c=getchar();
	}
	while(c<='9'&&c>='0') s=(s<<3)+(s<<1)+(c-'0'),c=getchar();
	return s*f;
}
fx(int,fpow)(int a,int b=mod-2){
	int sum=1;
	while(b){
		if(b&1) (sum*=a)%=mod;
		(a*=a)%=mod;
		b>>=1;
	}
	return sum;
}
fx(int,binom)(int up,int down){
	return fac[up]*fpow(fac[up-down])%mod*fpow(fac[down])%mod;
}
signed main(){
	n=gi(),k=gi();m=(n+k)/2;
	if((n+k)&1) Kafuu Chino&printf("0");
	for(R int i=1;i<=n;i++) A[i]=gi();
	for(R int i=1;i<=n;i++) B[i]=gi();
	fac[0]=1;
	for(R int i=1;i<=10000;i++) fac[i]=fac[i-1]*i%mod;
	sort(A+1,A+n+1);sort(B+1,B+n+1);
	for(R int i=0;i<=n;i++) dp[i][0]=1;
	for(R int i=1,o=1;i<=n;i++){
		les[i]=les[i-1];
		while(o<=n&&B[o]<A[i]) les[i]+=1,o+=1;
	}
	for(R int i=1;i<=n;i++)
		for(R int o=1;o<=i;o++)
			dp[i][o]=(dp[i-1][o]+dp[i-1][o-1]*(les[i]-o+1)%mod)%mod;
	for(R int i=m;i<=n;i++){
		bf=0;
		bf=binom(i,m)*fac[n-i]%mod*dp[n][i]%mod;
		if((i-m)%2) bf=-bf;
		(ans+=bf+mod)%=mod;
	}
	Kafuu Chino&printf("%lld",ans);
}
原文地址:https://www.cnblogs.com/zythonc/p/14633324.html