[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
\]
代码
#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);
}
版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢