二项式反演(学习笔记)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sizeof_you/article/details/86365003

q w q qwq 机房最后一个学二项式反演的人

众所周知 二项式反演可以表示成
f n = i = 0 n ( 1 ) i × C n i × g i g n = i = 0 n ( 1 ) i × C n i × f i f_n=\sum_{i=0}^n (-1)^i\times C_n^i\times g_i\Longleftrightarrow g_n=\sum_{i=0}^n(-1)^i\times C_n^i \times f_i

是一个极其对称的式子,常用表达是
f n = i = 0 n C n i × g i g n = i = 0 n ( 1 ) n i × C n i × f i f_n=\sum_{i=0}^n C_n^i\times g_i\Longleftrightarrow g_n=\sum_{i=0}^n(-1)^{n-i}\times C_n^i \times f_i

网上有很多很好的证明,比如这个博客,感觉容斥的证明比较形象
我就不写证明了(其实是懒得证

这里只写一些套路的做法和题目

恰好和至多的转换

如果要求 b l a b l a blabla 恰好有 k k b l a b l a blabla 的时候,有时候会很难算,而求至多有 k k b l a b l a blabla 的时候会很好算,最经典的就是错排问题,错排问题好像有递推,但二项式反演也是可以做的

f i f_i 表示恰好的方案数, g i g_i 表示至多的方案数,则有
g n = i = 0 n C n i × f i g_n=\sum_{i=0}^n C_n^i\times f_i
根据二项式反演有
f n = i = 0 n ( 1 ) n i × C n i × g i f_n=\sum_{i=0}^n(-1)^{n-i}\times C_n^i\times g_i
然后 g i g_i 一般在很短的时间内就可以方便求出,再用 g g f f 就可以得到答案

例题:
hdu1465不容易系列之一

错排问题
f i f_i 表示恰好有 i i 个错开, g i g_i 表示至多有 i i 个错开, g i = i g_i=i! ,然后套上面那个公式就好了
代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
#define N 25
using namespace std;

int n;
LL fac[N],f[N],C[N][N];

inline void prework(){
	fac[0]=1;
	for(int i=1;i<=20;i++) fac[i]=fac[i-1]*i;
	C[0][0]=1;
	for(int i=1;i<=20;i++) C[i][0]=1;
	for(int i=1;i<=20;i++)
		for(int j=1;j<=i;j++)
			C[i][j]=C[i-1][j]+C[i-1][j-1];
	
	for(int i=1;i<=20;i++)
		for(int j=0;j<=i;j++)
			if((i-j)&1) f[i]-=C[i][j]*fac[j];
			else f[i]+=C[i][j]*fac[j];
}

int main(){
	prework();
	while(~scanf("%d",&n)){
		printf("%lld\n",f[n]);
	}
	return 0;
}

恰好和至少的转换

同样有时候至少有 k k b l a b l a blabla 的要更好求

f i f_i 表示恰好的方案数, g i g_i 表示至少的方案数,则有
g k = i = k n C i k × f i g_k=\sum_{i=k}^nC_i^k\times f_i
根据二项式反演有
f k = i = k n ( 1 ) i k × C i k × g i f_k=\sum_{i=k}^n(-1)^{i-k}\times C_i^k\times g_i

例题:
bzoj3622: 已经没有什么好害怕的了
首先因为要求是 a &gt; b a&gt;b k k 个,所以 a &gt; b a&gt;b 的应该有 n + k 2 \frac{n+k}{2} 个(千万别读错题

为了打着舒服设 f i f_i 表示至少有 i i a &gt; b a&gt;b 的方案数,这个不能直接算,所以考虑 d p dp ,把 a , b a,b 从小到大排序,设 f [ i ] [ j ] f[i][j] 表示前 i i 个至少有 j j a &gt; b a&gt;b 的方案数,只需要算出来比当前 a [ i ] a[i] 小的 b b 有多少个,再减一下前面用过的,式子就是 f [ i ] [ j ] = f [ i 1 ] [ j 1 ] × ( c n t [ i ] j + 1 ) + f [ i 1 ] [ j ] f[i][j]=f[i-1][j-1]\times (cnt[i]-j+1)+f[i-1][j]

算出来以后再用 f [ n ] [ i ] f[n][i] 带入上面的式子里去算最终恰好的答案
注意 f [ n ] [ i ] f[n][i] 用的时候还要 × ( n i ) ! \times (n-i)! ,因为其他位置是随便放的

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
#define N 2005
using namespace std;

template<class T>inline void rd(T &x){
	x=0; short f=1; char c=getchar();
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	x*=f;
}

const int mod=1e9+9;
int n,k,C[N][N],f[N][N],a[N],b[N],cnt[N],ans,fac[N];

inline void prework(int n){
	C[0][0]=1;
	for(int i=1;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;
	fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%mod;
}

int main(){
	rd(n); rd(k);
	if((n+k)&1) return puts("0"),0;
	prework(n); k=(n+k)>>1;
	for(int i=1;i<=n;i++) rd(a[i]);
	for(int i=1;i<=n;i++) rd(b[i]);
	sort(a+1,a+n+1); sort(b+1,b+n+1);
	int now=0;
	for(int i=1;i<=n;i++){
		while(b[now+1]<a[i] && now<n) now++;
		cnt[i]=now;
	}
	for(int i=0;i<=n;i++) f[i][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			f[i][j]=(1LL*f[i-1][j-1]*max((cnt[i]-j+1),0)%mod+1LL*f[i-1][j])%mod;
	for(int i=k;i<=n;i++){
		f[n][i]=1LL*f[n][i]*fac[n-i]%mod;
		if((i-k)&1) (ans+=mod-1LL*C[i][k]*f[n][i]%mod)%=mod;
		else (ans+=1LL*C[i][k]*f[n][i]%mod)%=mod;
	}
	printf("%d\n",ans);
	return 0;
}

bzoj2839: 集合计数
首先还是设 f i f_i 表示恰好交集有 i i 个元素的, g i g_i 表示至少的,那么 g i = C n i × ( 2 2 n i 1 ) g_i=C_n^i\times (2^{2^{n-i}}-1)
这里的含义就是钦定 i i 个必须选,剩下的可以选可以不选的有 2 n i 2^{n-i} 个集合,这些集合可选可不选,但不能都不选,就有了上式

再套用上面的公式得出
f k = i = k n ( 1 ) i k × C i k × C n i × ( 2 2 n i 1 ) f_k=\sum_{i=k}^n(-1)^{i-k}\times C_i^k\times C_n^i\times (2^{2^{n-i}}-1)
O ( n ) O(n) 计算即可,注意后面那个 2 2 n i 2^{2^{n-i}} 貌似不能直接快速幂,要用 2 2 t = ( 2 2 t 1 ) 2 2^{2^t}=(2^{2^{t-1}})^2 倒着枚举算

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 1000005
#define LL long long
#define int LL
using namespace std;

int n,k,fac[N],inv[N];
LL ans;
const int mod=1000000007;

inline int qpow(int x,int k){
	int ret=1;
	while(k){
		if(k&1) ret=1LL*ret*x%mod;
		x=1LL*x*x%mod; k>>=1;
	} return ret;
}

inline void prework(){
	fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%mod;
	inv[n]=qpow(fac[n],mod-2); for(int i=n;i;i--) inv[i-1]=1LL*inv[i]*i%mod;
}

inline LL C(int n,int m){return 1LL*fac[n]*inv[m]%mod*inv[n-m]%mod;}

signed main(){
	scanf("%lld%lld",&n,&k); prework();
	int bin=2;
	for(int i=n;i>=k;i--){
		if((i-k)&1) (ans+=mod-C(i,k)*C(n,i)%mod*(bin-1)%mod)%=mod;
		else (ans+=C(i,k)*C(n,i)%mod*(bin-1)%mod)%=mod;
		bin=1LL*bin*bin%mod;
	}
	printf("%lld\n",(ans+mod)%mod);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/sizeof_you/article/details/86365003