二项式反演学习笔记

二项式反演学习笔记

二项式反演(Binomial Inversion)是一种反演,它基于容斥原理.它可以把计数问题中求解"恰好X个的方案数"转化为求解"至少X个的方案数",让问题变得更简单。

约定:

  1. (C_n^m)表示从(n)个数里选(m)个数的方案数,等价于(inom{m}{n}).另外为了方便推式子,规定(n<m)(C_n^{m}=0)
  2. (S)是一个集合,规定(overline{S})(S)的补集
  3. (S)是一个集合,规定(|S|)(S)的元素个数
  4. ([p(x)]=egin{cases} 1,p(x) \ 0, eg p(x) end{cases})

基本定理

二项式反演有3种基本形式:

[f_n = sum_{i=0}^n (-1)^i C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^i C_n^i f_i ]

[f_n = sum_{i=0}^n C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^{n-i} C_n^i f_i ]

[forall m geq n,f_n = sum_{i=n}^m C_i^n g_i Leftrightarrow g_n = sum_{i=n}^m (-1)^{i-n} C_i^n f_i ]

接下来我们会证明它。

标准形式

定理1(二项式反演形式1):$$f_n = sum_{i=0}^n (-1)^i C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^i C_n^i f_i ag{1.1}$$

这个式子充满了对称性,接下来我们证明它

容斥解释

实际上,二项式反演是容斥原理的特例。
我们可以构造一些集合来证明这个定理,全集为(S),(S)中具有(n)种性质的集合分别为(A_1,A_2 dots A_n).特别地,我们构造这些集合,使它们满足其中任意(i)个集合的并集大小相等,记为(g_i).另外规定(g_0=|S|)

那么根据容斥原理,均没有这些性质的集合的大小为:

[egin{aligned}|overline{A_1} cap overline{A_2} cap cdots cap overline{A_n}|= |S| - sum |A_i| + sum |A_i cap A_j| + cdots + (-1)^n sum |A_1 cap A_2 cap cdots cap A_n| end{aligned} ]

又因为任意(i)个集合的并集大小相等

[egin{aligned}&|S| - sum |A_i| + sum |A_i cap A_j| + cdots + (-1)^n sum |A_1 cap A_2 cap cdots cap A_n| \ &=g_0-C_n^1 g_1+C_n^2g_2+ dots + (-1)^n C_n^n g_n end{aligned} ]

同理可得(A_i)的补集的并集大小相等,不妨记为(f_i),同样令(f_0=|S|).那么上式可化为$f_n = sum_{i=0}^n (-1)^i C_n^i g_i $.
再使用一次容斥原理:

[egin{aligned} & & &|A_1 cap A_2 cap cdots cap A_n| \ &=& &|S| - sum |overline{A_i}| + sum |overline{A_i} cap overline{A_j}| \ & &+& cdots + (-1)^n sum |overline{A_1} cap overline{A_2} cap cdots cap overline{A_n}|\ &=& &f_0 - C_n^1 f_1 + C_n^2 f_2 + cdots + (-1)^n C_n^n f_n end{aligned}]

那么$g_n = sum_{i=0}^n (-1)^i C_n^i f_i $

这说明,两个式子是等价的。因此我们就证明了反演定理

代数解释

虽然容斥解释很妙,但是在题目中我们可能会遇到非标准的式子,因此要理解代数展开证明的思想。

已知$f_n = sum_{i=0}^n (-1)^i C_n^i g_i $
那么

[egin{aligned}sum_{i=0}^n (-1)^i C_n^i f_i &=sum_{i=0}^n (-1)^iC_n^i sum_{j=0}^i(-1)^j C_i^j g_j \ &=sum_{j=0}^n g_j sum_{i=j}^n (-1)^{i+j}C_{n}^i C_{i}^jend{aligned} ]

第二步是交换了求和顺序,我们尝试对于每个(f_j)计算出它的系数并提到外面,容易发现对于(j in [1,i])都会乘上系数((-1)^iC_n^i),也就是说会被(>=j)((-1)^iC_n^i)更新。
那么这样只需要后面的和式在(j eq n)时为0,(j=n)时为1,那么总和就为(g_n)

接下来需要用到组合数的性质

[egin{aligned} C_n^i C_j^i &=frac{n!}{(n-i)!i!}frac{i!}{(i-j)!j!} \ &=frac{n!}{(n-i)!(i-j)!j!} \ &=frac{n!(n-j)!}{j!(n-j)!(n-i)!(i-j)!} ( ext{ 为了构造组合数,上下同乘}(n-j)!) \ &=frac{n!}{j!(n-j)!} frac{(n-j)!}{(n-i)![(n-i)-(n-j)]!} \ &= C_n^j C_{n-j}^{n-i} (1.1.2)end{aligned} ]

代回上式

[egin{aligned} sum_{i=j}^n (-1)^{i+j}C_{n}^i C_{i}^j &=(-1)^jC_n^j sum_{i=j}^n (-1)^i C_{n-j}^{n-i} \ &=(-1)^jC_n^j sum_{i=0}^{n-j} (-1)^{n-i} C_{n-j}^{i} ( ext{把}i ext{换成}n-i) \ &= (-1)^jC_n^j (1-1)^{n-j} ( ext{二项式定理})end{aligned} ]

(n eq j)( ext{原式}=0)
(n = j)时出现了(0^0)无法直接算,考虑直接用原式(因为最后一步转化破坏了等价性),(sum_{i=j}^n (-1)^{i+j}C_{n}^i C_{i}^j=sum_{i=n}^n (-1)^{i+n}C_{n}^n C_{i}^n=(-1)^{2n}C_n^nC_n^n=1)
因此( ext{原式}=[n=j])
也就是说

[egin{aligned}sum_{i=0}^n (-1)^i C_n^i f_i =sum_{j=0}^n g_j [n=j]=g_nend{aligned} ]

至多和恰好的转化

定理2(二项式反演形式2):$$f_n = sum_{i=0}^n C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^{n-i} C_n^i f_i ag{1.2}$$

这个式子的意义是,(f_n)表示至多(n)个的方案数,那么(g_n)就表示恰好(n)个的方案数。

证明:
考虑形式1

[f_n = sum_{i=0}^n (-1)^i C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^i C_n^i f_i ]

(g'_i=(-1)^{-i} g_i,那么f_n= sum_{i=0}^n (-1)^i C_n^i g'_i)
那么(g'_n = sum_{i=0}^n (-1)^i C_n^i f_i)
((-1)^{-n}g_n=sum_{i=0}^n (-1)^i C_n^i f_i)
两边同乘((-1)^{-2n}=1),得到$g_n=sum_{i=0}^n (-1)^{i-n} C_n^i f_i=sum_{i=0}^n (-1)^{n-i} C_n^i ( ) (forall x in mathbb{Z},(-1)^{-x}=(-1)^{2x}(-1)^{-x}=(-1)^x)$

至少和恰好的转化

这是最常见的二项式反演形式,一定要掌握!

定理3(二项式反演形式3):$$f_n = sum_{i=n}^m C_i^n g_i Leftrightarrow g_n = sum_{i=n}^m (-1)^{i-n} C_i^n f_i ag{1.3}$$
对于(forall m geq n),结论都成立,即使是一个无限求和也可以

这个式子的意义是,(f_n)表示 "至少" (n)个的方案数,那么(g_n)就表示恰好(n)个的方案数.这里的 "至少"打引号是因为它不是一个简单的后缀和,它表示我们"钦定"(n)个数满足条件,剩下的数不一定满足条件(f)的时候每个(g_i)是会被重复计算的,计算了(C_n^i)次.

证明:

[egin{aligned} sum_{i=n}^m (-1)^{i-n} C_i^n f_i &= sum_{i=n}^m (-1)^{i-n} C_i^n sum_{j=i}^m C_j^i f_jend{aligned} ]

((1.1))的证明,交换求和顺序

[egin{aligned} ext{原式} &= sum_{j=n}^m f_j sum_{i=n}^j (-1)^{i-n} C_{j}^i C_i^nend{aligned} ]

注意到后面的(sum_{i=n}^j (-1)^{i-n} C_{j}^i C_i^n)部分也很相似,因此可以用类似的方法处理。
代入组合数的性质((1.1.2))

[egin{aligned} sum_{i=n}^j (-1)^{i-n} C_{j}^i C_i^n &= sum_{i=n}^k C_j^n C_{j-n}^{j-i} \ &= (-1)^{-n}C_n^j sum_{i=n}^j (-1)^i C_{j-n}^{j-i} \ &= (-1)^{-n}C_n^j sum_{i=0}^{j-n} (-1)^{j-i} C_{j-n}^{i} \ &= (-1)^{-n}C_n^j (1-1)^{j-n} \&=[n=j] end{aligned} ]

容易发现这个证明过程只是把原来的(n-j)换成(j-n)而已。

*广义的反演

我们发现形式(1)和形式(3)的证明有相似之处,都是交换求和顺序然后提取出和式,证明它等于([n=k]). 我们不妨将这个方法推广到任意序列。

定理4
命题$$f_n = sum_{i=0}^n a_{n,i} g_i Leftrightarrow g_n = sum_{i=0}^n b_{n,i} f_i $$成立的充要条件是

[forall i in mathbb{N},sum_{j=i}^n b_{n,j}a_{j,i}=[n=i] ]

证明:
还是交换求和顺序

[sum_{i=0}^n b_{n,i} f_i=sum_{i=0}^n b_{n,i} sum_{j=0}^i a_{i,j}g_j=sum_{j=1}^n g_j sum_{i=j}^n b_{n,j} a_{i,j} ]

把字母换一下就得到了上面的定理

应用

在做题中,看到求"恰好X"的方案数,就要敏感的想到二项式反演。

形式1几乎没有什么用

形式2(至多)的应用

错排问题

求错排问题的通项公式:
若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 (n)个元素的错排数记为(D_n)

[D_n=n!sum_{i=0}^n frac{(-1)^i}{i!} ]

其实可以把(D_n)看成不在原来位置上的元素有(n)个的排列数。设(f_n)表示长度为n的排列,在原来位置上的元素至多有(n)个的排列数,显然所有排列都满足这个性质,即(f_n=n!)

那么

[egin{aligned} sum_{i=0}^n (-1)^{n-i} C_n^i f_i &= sum_{i=0}^n (-1)^{n-i} frac{n!}{i!(n-i)!}i! \ &=sum{i=0}^n n! frac{(-1)^{n-i}}{(n-i)!} \ &= n!sum_{i=0}^n frac{(-1)^i}{i!}end{aligned} ]

min-max容斥

(S)为一个集合,(max(S),min(S))分别表示集合中的最大值和最小值。特别地,规定(max(emptyset)=min(emptyset)=0),则$$max(S)=sum_{Tsubset S}(-1)^{|T|-1} min(T)$$

证明:
设存在一个以集合大小为自变量的函数(f)满足

[max(S)=sum_{Tsubset S}f(|T|) min(T) ]

(S)中元素从大到小分别为(a_1,a_2 dots a_{|S|}).
那么(a_i)对等式左边的贡献是([i=1]),因为(a_1=max(S)).
(a_i)对等式右边的贡献为(sum_{j=0}^{i-1}C_{i-1}^j f(j+1)). 这是因为要(i)成为大小为(j+1)的集合的集合中的最小值,就要从(a_1,a_2 dots a_{i-1})这些比(i)大的数中选出(j)个,再加上(a_i)构成一个集合。

那么为了等式成立,左右的贡献应该两两抵消,只剩下(a_1).所以必有

[[i=1]=sum_{j=0}^{i-1}C_{i-1}^j f(j+1) ]

构造函数(A(x)=[x=0]),那么(A(x-1)=[x=1]). 注意到右边有(f(j+1)),不方便反演,不妨令(B(x)=f(x+1))

那么(A(i)=sum_{j=0}^i C_i^j B(j))
反演得到(B(i)=sum_{j=0}^i (-1)^{n-j} C_i^jA(j)=sum_{j=0}^i (-1)^{n-j} C_i^j[j=0]=(-1)^{i})

所以(f(x)=B(x-1)=(-1)^{x-1}),代回原式,得

[max(S)=sum_{Tsubset S}(-1)^{|T|-1} min(T) ]

min-max容斥有许多应用,这里不再赘述。

形式3(至少)的应用

由于形式3的(f)定义是可以有重复计数的,因此它会比"恰好"的形式好求,因为不需要考虑计数时的一些判重问题。这也给求(f)的过程(如直接数学计算/DP)提供了方便。

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

(f_i)表示交集的元素个数 "至少"(i)的方案数。那么我们可以从(n)个元素中选出(i)个钦定为交集。剩下的(n-i)个元素组成(2^{n-i})包含空集的集合。从这些集合中任意选一些集合并上那(i)个数,它们的交集一定是这(i)个数。
因此

[f_i=C_n^i (2^{2^{n-i}}-1) ]

根据二项式反演,答案为

[sum_{i=k}^n (-1)^{i-k}C_{i}^kf_i ]

注意(2^{2^{n-i}})无法快速幂,只需要递推求即可。因为(2^{2^i}=2^{2^{i-1}} cdot2^{2^{i-1}})

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 1000000007
#define maxn 1000000
using namespace std;
typedef long long ll;
inline ll fast_pow(ll x,ll k){
	ll ans=1;
	while(k){
		if(k&1) ans=ans*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return ans;
}
inline ll inv(ll x){
	return fast_pow(x,mod-2);
}
ll fact[maxn+5],invfact[maxn+5];
void ini(int n){
	fact[0]=1;
	for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
	invfact[n]=inv(fact[n]);
	for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
}
ll C(int n,int m){
	return fact[n]*invfact[n-m]%mod*invfact[m]%mod;
} 

int n,k;
ll f[maxn+5],g[maxn+5];
int main(){
	scanf("%d %d",&n,&k);
	ini(n);
	ll pw=2;//2^(2^(n-i)),无法快速幂计算,只能递推 
	for(int i=n;i>=0;i--){
		f[i]=C(n,i)*(pw-1)%mod;
		pw=pw*pw%mod;
	}
	ll ans=0;
	for(int i=k;i<=n;i++){
		if((i-k)%2==1) ans=ans-C(i,k)*f[i]%mod;
		else ans=ans+C(i,k)*f[i]%mod;
		ans=(ans+mod)%mod;
	}
	printf("%lld
",ans);
}

[LuoguP4859]已经没有什么好害怕的了
(这里是简化版的题意)给出两个长度为(n)的序列(a,b),现在我们要把每个(a)中元素与每个(b)中元素配对,问满足(a_x>b_y)的配对数比(a_x<b_y)的配对数多(k)的配对方案数

首先问题转化为(a>b)的个数有(frac{n+k}{2})

(f(i))表示保证(钦定)有(i)(a>b),其余不一定。(g(i))表示恰好(i)对a>b.显然(f(i)=sum_{j=i}^m C_j^i g(j)),根据二项式反演定理(g(i)=sum_{j=i}^m C_{j}^i f(j))

考虑如何求(f).把(a)从小到大排序,记(cnt(i))表示(b)中小于(a_i)的数的个数。设(dp_{i,j})(a)序列的前(i)个,保证有(j)(a>b)的方案数,则(f_i=dp_{n,i}(n-i)!)

容易写出转移方程:
(dp_{i,j}=dp_{i,j-1}+dp_{i-1,j-1}cdot (cnt(i)-(j-1)))

我们按(a)排序,因此(b)中小于(a_i)的数的集合一定包含(b)中小于(a_{i-1})的数的集合.之前已经选了(j-1)个数,那只能选剩下的(cnt(i)-(j-1))个数来使得(a_i>b_x),这样对数会增加1。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 1000000009
#define maxn 2000
using namespace std;
typedef long long ll;
inline ll fast_pow(ll x,ll k){
	ll ans=1;
	while(k){
		if(k&1) ans=ans*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return ans;
}
inline ll inv(ll x){
	return fast_pow(x,mod-2);
}
ll fact[maxn+5],invfact[maxn+5];
void ini(int n){
	fact[0]=1;
	for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
	invfact[n]=inv(fact[n]);
	for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
}
ll C(int n,int m){
	return fact[n]*invfact[n-m]%mod*invfact[m]%mod;
} 

int n,k;
int a[maxn+5],b[maxn+5];
int cnt[maxn+5];//a从小到大排序后,a[i]>b[j]的j的数量 
ll dp[maxn+5][maxn+5];
ll f[maxn+5],g[maxn+5];

void calc_inv(ll *f,ll *g,int n){
	for(int i=0;i<=n;i++){
		for(int j=i;j<=n;j++){
			if((j-i)%2==1) g[i]-=C(j,i)*f[j]%mod;
			else g[i]+=C(j,i)*f[j]%mod;
			g[i]=(g[i]+mod)%mod;
		}
	}
} 
int main(){
	scanf("%d %d",&n,&k);
	ini(n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	sort(a+1,a+1+n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++) if(a[i]>b[j]) cnt[i]++;
	}
	for(int i=0;i<=n;i++) dp[i][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			dp[i][j]=(dp[i-1][j-1]*max(cnt[i]-j+1,0)%mod+dp[i-1][j])%mod;
		}
	}
	for(int i=0;i<=n;i++) f[i]=dp[n][i]*fact[n-i]%mod;
	calc_inv(f,g,n);
	printf("%lld
",g[(n+k)/2]);
} 

[NOIOnline2提高组T3]游戏
小 A 和小 B 正在玩一个游戏:有一棵包含 n=2m个点的有根树(点从1∼n 编号),它的根是 1 号点,初始时两人各拥有 m 个点。游戏的每个回合两人都需要选出一个自己拥有且之前未被选过的点,若对手的点在自己的点的子树内,则该回合自己获胜;若自己的点在对方的点的子树内,该回合自己失败;其他情况视为平局。游戏共进行 m 回合。
对于 k=0,1,2,⋯,m,计算出非平局回合数为 k 的情况数。两种情况不同当且仅当存在一个小 A 拥有的点 x,小 B 在 x 被小 A 选择的那个回合所选择的点不同。

二项式反演的套路,设(f(i))为非平局回合数至少为(i)的情况. (g(i))为非平局回合数恰好为(i)的情况,则(f(i)=sum_{j=i}^m C_j^i g(j)).(这里的"至少"指的是我们"钦定"有(i)局一定平局,其他不一定,所以至少为(i).因此对(f(i))求后缀和不是(g(i)),会算重)我们要求的是(g(k)),但是(g)不太好求,考虑求(f)再反演得出(g).根据二项式反演定理(g(i)=sum_{j=i}^m C_{j}^i f(j))

(f)可以用树形背包来求。(dp_{x,i})(x)的子树内至少(i)对选择的有祖先-后代关系的点(即非平局的回合).那么(f(i)=dp_{1,i}cdot(m-i)!)(剩下的(m-i)对关系可以任意排序)

DP的时候先用模板的树形背包合并子树,然后考虑(x)这个点对答案的影响,有
(dp_{x,i} leftarrow dp_{x,i}+dp_{x,i-1}cdots( ext{x子树内不被x的拥有者拥有的节点个数}-(i-1)))
这是因为(x)可以和子树内不被(x)的拥有者拥有的,且还没被配对(减去(i-1))的节点形成非平局回合。

#include<iostream>
#include<cstdio>
#include<cstring>
#define mod 998244353
#define maxn 5000
using namespace std;
typedef long long ll;
inline ll fast_pow(ll x,ll k){
	ll ans=1;
	while(k){
		if(k&1) ans=ans*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return ans;
}
inline ll inv(ll x){
	return fast_pow(x,mod-2);
}
ll fact[maxn+5],invfact[maxn+5];
void ini(int n){
	fact[0]=1;
	for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
	invfact[n]=inv(fact[n]);
	for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
}
ll C(int n,int m){
	return fact[n]*invfact[n-m]%mod*invfact[m]%mod;
} 

int m,n;
int a[maxn+5];
struct edge {
	int from;
	int to;
	int next;
} E[maxn*2+5];
int head[maxn+5];
int esz=1;
void add_edge(int u,int v) {
	esz++;
	E[esz].from=u;
	E[esz].to=v;
	E[esz].next=head[u];
	head[u]=esz;
}

int sz[maxn+5];
int cnt[maxn+5][2];
ll dp[maxn+5][maxn+5];//x的子树内,至少有i对祖先关系的方案数(有i对我们已经钦定,剩下的随便搞 
//在dp转移中,我们钦定i对关系来转移,最后再乘上(m-i)!,表示剩下的关系可以任意匹配 
//因为只是钦定,转移的时候就不需要考虑剩下的关系。而如果直接定义状态为恰好i对,转移时无法保证合法 
void dfs(int x,int fa) {
	static ll tmp[maxn+5];
	sz[x]=1;
	cnt[x][a[x]]++;
	dp[x][0]=1;
	for(int i=head[x];i;i=E[i].next){//先合并子树 
		int y=E[i].to;
		if(y!=fa){
			dfs(y,x);
			for(int j=0;j<=sz[x]+sz[y];j++) tmp[j]=0;
			for(int j=0;j<=sz[x];j++){
				for(int k=0;k<=sz[y];k++){
					tmp[j+k]=(tmp[j+k]+dp[x][j]*dp[y][k]%mod)%mod;
				}
			}
			for(int j=0;j<=sz[x]+sz[y];j++) dp[x][j]=tmp[j];
			sz[x]+=sz[y];
			cnt[x][0]+=cnt[y][0];
			cnt[x][1]+=cnt[y][1];
		}
	}
	//计算x的贡献 
	for(int i=cnt[x][a[x]^1];i>=1;i--){//对于dp[x][i],x可以和子树里还没被选的与a[x]颜色相反的点匹配 
		//于是贡献为dp[x][i-1]*(cnt[x][a[x]^1]-(i-1)) 倒序是为了防止重复更新 
		dp[x][i]+=dp[x][i-1]*(cnt[x][a[x]^1]-(i-1))%mod;
		dp[x][i]%=mod;
	}
}

ll f[maxn+5],g[maxn+5];
int main() {
	int u,v;
	scanf("%d",&n);
	m=n/2;
	ini(n);
	for(int i=1;i<=n;i++) scanf("%1d",&a[i]);
	for(int i=1;i<n;i++){
		scanf("%d %d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	for(int i=0;i<=m;i++) f[i]=dp[1][i]*fact[m-i]%mod;//求出真正的dp值
	for(int i=0;i<=m;i++){
		for(int j=i;j<=m;j++){
			if((j-i)%2==1) g[i]-=C(j,i)*f[j]%mod;
			else g[i]+=C(j,i)*f[j]%mod;
			g[i]=(g[i]+mod)%mod;
		}
		printf("%lld
",g[i]);
	} 
}
原文地址:https://www.cnblogs.com/birchtree/p/12811308.html