[BZOJ2839]集合计数

[BZOJ2839]集合计数

题目大意:

一个有\(n(n\le10^6)\)个元素的集合有\(2^n\)个不同子集,现在要在这\(2^n\)个集合中取出若干集合(至少一个),使得
它们的交集的元素个数为\(k\),求取法的方案数。

思路:

容斥原理。

答案为大于等于\(k\)的方案数-大于等于\(k+1\)的方案数+大于等于\(k+2\)的方案数……

其中大于等于\(k\)的方案数为\({n\choose k}(2^{2^{n-k}}-1)\)

\(2^{2^{n-k}}=(2^{2^{n-k-1}})^2\),因此时间复杂度可以做到\(\mathcal O(n)\)

源代码:

#include<cstdio>
#include<cctype>
inline int getint() {
	register char ch;
	while(!isdigit(ch=getchar()));
	register int x=ch^'0';
	while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
	return x;
}
typedef long long int64;
const int N=1e6+1,mod=1e9+7;
int fac[N],ifac[N];
void exgcd(const int &a,const int &b,int &x,int &y) {
	if(!b) {
		x=1,y=0;
		return;
	}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
inline int inv(const int &x) {
	int ret,tmp;
	exgcd(x,mod,ret,tmp);
	return (ret%mod+mod)%mod;
}
inline int C(const int &n,const int &m) {
	return (int64)fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main() {
	const int n=getint(),k=getint();
	for(register int i=fac[0]=1;i<=n;i++) {
		fac[i]=(int64)fac[i-1]*i%mod;
	}
	ifac[n]=inv(fac[n]);
	for(register int i=n;i>=1;i--) {
		ifac[i-1]=(int64)ifac[i]*i%mod;
	}
	int ans=0;
	for(register int i=n,pwr=2;i>=k;i--) {
		int tmp=(int64)C(n-k,i-k)*(pwr-1)%mod;
		if((i-k)&1) tmp=mod-tmp;
		(ans+=tmp)%=mod;
		pwr=(int64)pwr*pwr%mod;
	}
	ans=(int64)ans*C(n,k)%mod;
	printf("%d\n",ans);
	return 0;
}
posted @ 2018-09-17 13:48  skylee03  阅读(276)  评论(0编辑  收藏  举报