BZOJ2839 集合计数【二项式反演】

题目链接:https://darkbzoj.tk/problem/2839

题意:
一个有N个元素的集合有 2 N 个 不 同 子 集 ( 包 含 空 集 ) 2^{N个不同子集(包含空集)} 2N,现在要在这 2 N 2^N 2N个集合中取出若干集合(至少一个),使得
它们的交集的元素个数为K,求取法的方案数

题解:
f [ K ] f[K] f[K]表示交集大小 ≥ K \ge K K的方案数,则 f [ K ] = C ( n , k ) ∗ ( 2 2 n − i − 1 ) f[K] = C(n,k)*(2^{2^{n-i}}-1) f[K]=C(n,k)(22ni1) (考虑钦定 k 个,剩下的随便选,有 2 n − i 2^{n-i} 2ni个集合,共 2 2 n − i 2^{2^{n-i}} 22ni种 且不能不选)
g [ K ] g[K] g[K]表示交集恰好为 K K K的方案数,则 f [ K ] = ∑ i = K n C ( n , i ) g [ i ] f[K]=\sum_{i=K}^nC(n,i)g[i] f[K]=i=KnC(n,i)g[i]
根据二项式反演, g [ K ] = ∑ i = k n ( − 1 ) i − k C i k f ( i ) g[K] = \sum_{i=k}^n(-1)^{i-k}C_i^kf(i) g[K]=i=kn(1)ikCikf(i)
然后 2 2 X 2^{2^X} 22X用费马小定理,等于 2 ( 2 x ) % ( m o d − 1 ) 2^{(2^x) \% (mod-1)} 2(2x)%(mod1)两次快速幂即可

代码:

// by Balloons
#include 
#include 
#include 
#include 
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1e9, maxn=1e6+5, mod=1e9+7;

int n,k;
int neg(int x){if(x&1)return -1;return 1;}
int f[maxn],fac[maxn],inv[maxn];

int pw(int x,int y,int md){
	if(y == 0)return 1;if(y == 1)return x;
	int mid=pw(x,y>>1,md);
	if(y&1)return 1ll*mid*mid%md*x%md;
	return 1ll*mid*mid%md;
}

int C(int x,int y){
	if(x<y)return 0;
	return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;
}

int main(){
	fac[0] = inv[0] = 1;
	for(int i=1;i<=maxn-5;i++)fac[i] = 1ll*fac[i-1]*i%mod;
	inv[maxn-5] = pw(fac[maxn-5],mod-2,mod);
	for(int i=maxn-6;i>=1;i--)inv[i] = 1ll*inv[i+1]*(i+1)%mod;
	
	scanf("%d%d",&n,&k);
	for(int i=0;i<=n;i++){
		f[i] = 1ll*C(n,i)*(pw(2,pw(2,n-i,mod-1),mod)-1)%mod;
	}
	LL ans = 0;
	for(int i=k;i<=n;i++){
		ans = (ans + 1ll*neg(i+k)*C(i,k)%mod*f[i]%mod)%mod;
		ans = (ans+mod)%mod;
//		printf("%lld\n",ans);getchar();
	}
	printf("%lld\n",ans);

	return 0;
}

你可能感兴趣的:(【解题报告】,====组合数学)