[BZOJ2839] 集合计数(二项式反演)

[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\n",ans);
}

猜你喜欢

转载自www.cnblogs.com/birchtree/p/12791661.html