二项式反演在需要容斥问题的中经常出现,也是很多知识的基础。
引入
首先设 A1,A2,…,An 是 n 个集合,那么有:
∣Ai∪A2∪⋯∪An∣=(1≤i≤n∑∣Ai∣)−(1≤i<j≤n∑∣Ai∩Aj∣)+⎝⎛1≤i<j<k≤n∑∣Ai∩Aj∩Ak∣⎠⎞−⋯+(−1)n−1×∣A1∩A2∩⋯∩An∣
这其实很好理解,就是减掉算重一次的,加上算重两次的,减掉算重三次的……
变形一下,有:(Aic 是 Ai 的补集,S 是全集)
∣Aic∩A2c∩⋯∩Anc∣=∣S∣−(1≤i≤n∑∣Ai∣)+(1≤i<j≤n∑∣Ai∩Aj∣)−⎝⎛1≤i<j<k≤n∑∣Ai∩Aj∩Ak∣⎠⎞+⋯+(−1)n×∣A1∩A2∩⋯∩An∣
由于补集的补集是原集,所以有:
∣Ai∩A2∩⋯∩An∣=∣S∣−(1≤i≤n∑∣Aic∣)+(1≤i<j≤n∑∣Aic∩Ajc∣)−⎝⎛1≤i<j<k≤n∑∣Aic∩Ajc∩Akc∣⎠⎞+⋯+(−1)n×∣A1c∩A2c∩⋯∩Anc∣
考虑一种特殊情况,集合的交集大小之和集合个数有关。设 f(n) 表示 n 个集合的补集的交集大小,g(n) 表示 n 个集合的交集大小,那么上面两条式子可以写成:
f(n)=i=0∑n(−1)i(in)g(i)
g(n)=i=0∑n(−1)i(in)f(i)
所以这两条式子是等价的关系,那么有:
f(n)=i=0∑n(−1)i(in)g(i)⟺g(n)=i=0∑n(−1)i(in)f(i)
这就是二项式反演的形式一。
更多形式
形式二
f(n)=i=0∑n(in)g(i)⟺g(n)=i=0∑n(−1)n−i(in)f(i)
证明:
令 h(n)=(−1)ng(n),那么带入形式一有 f(n)=i=0∑n(in)h(i)⟺(−1)nh(n)=g(n)=i=0∑n(−1)i(in)f(i),整理后即得证。
形式三
f(n)=i=n∑m(ni)g(i)⟺g(n)=i=n∑m(−1)i−n(ni)f(i)
证明:
组合意义是 f(n) 表示钦定 n 个选,g(n) 表示恰好选 n 个。因为 f(n) 同一种方案会计算多次,具体的,选 n 个的方案钦定选 i 个就会重复计算 (in) 次,所以 f(n)=i=n∑m(ni)g(i)。而
g(n)=i=n∑m(−1)i−n(ni)f(i)=i=n∑m(−1)i−n(ni)j=i∑m(ij)g(j)=j=n∑mg(j)i=n∑j(−1)i−n(ni)(ij)=j=n∑mg(j)i=n∑j(−1)i−nn!(i−n)!i!i!(j−i)!j!=j=n∑mg(j)i=n∑j(−1)i−nn!(i−n)!(j−i)!j!=j=n∑mg(j)i=n∑j(−1)i−nn!(i−n)!(j−i)!(j−n)!j!(j−n)!=j=n∑m(nj)g(j)i=n∑j(−1)i−n(i−nj−n)=j=n∑m(nj)g(j)i=0∑j−n(−1)i(ij−n)=j=n∑m(nj)g(j)(1−1)j−n=j=n∑m(nj)g(j)[j==n]=(nn)g(n)=g(n)
所以得证。
例题
bzoj 2839 集合计数
设 f(i) 表示钦定交集有 i 个的方案数,g(i) 表示交集恰好有 i 个的方案数,那么有 f(i)=(in)(22n−i−1),f(i)=j=i∑n(ij)g(j),反演一下得到 g(i)=j=i∑n(−1)j−i(ij)f(j),由于只要算一次,所以直接算就行了。
代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
const int S=1000005,p=1000000007;
int fra[S],inv[S];
int n,k;
int f[S];
inline int qpow(int x,int y,int p)
{
int res=1;
for(;y>0;y>>=1,x=1ll*x*x%p) res=(y&1)?1ll*res*x%p:res;
return res;
}
inline int C(int n,int m)
{
return 1ll*fra[n]*inv[n-m]%p*inv[m]%p;
}
int main()
{
fra[0]=1;
for(int i=1;i<=S-3;i++) fra[i]=1ll*fra[i-1]*i%p;
inv[S-3]=qpow(fra[S-3],p-2,p);
for(int i=S-3;i>=1;i--) inv[i-1]=1ll*inv[i]*i%p;
scanf("%d%d",&n,&k);
for(int i=0;i<=n;i++) f[i]=1ll*C(n,i)*((long long)qpow(2,qpow(2,n-i,p-1),p)-1+p)%p;
int ans=0;
for(int i=k;i<=n;i++)
{
int pre=1ll*C(i,k)*f[i]%p;
if(i-k&1) ans=(ans-pre+p)%p;
else ans=(ans+pre)%p;
}
printf("%d\n",ans);
return 0;
}
习题