[BZOJ2839]集合记数 容斥原理

BZOJ2839

  • 一个有N个元素的集合有 2 N 2^{N} 个不同子集(包含空集),现在要在这 2 N 2^N 个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 K K ,求取法的方案数,答案模1000000007。
  • 交集元素个数至少为 K K 的方案数好求,恰好元素个数为 K K 的方案数不好求。
  • f [ k ] f[k] 表示至少为 K K 的方案数, g [ k ] g[k] 表示恰好为 K K 的方案数。
  • f [ k ] = i = k n C i k g [ i ] f[k]=\sum_{i=k}^{n}C_{i}^{k}*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}^{k}*f[i]
  • 现在只需要考虑 f f 怎么求。 f [ k ] = C n k ( 2 2 n k 1 ) f[k]=C_{n}^{k}*(2^{2^{n-k}}-1)
  • 2 2 n k 2^{2^{n-k}} 不好直接求,根据欧拉定理的推论,把幂先模 φ ( m o d ) \varphi(mod) m o d 1 mod-1
  • O ( n ) O(n) 预处理一下 f f 数组, O ( n ) O(n) 求答案。

Coding

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e6+10;
const int mod=1e9+7;
int n,k;
ll inv[N],jie[N],njie[N],f[N],ans;
ll power(ll a,ll b,ll p){
	ll res=1%p;
	for(;b;b>>=1){
		if(b&1) res=res*a%p;
		a=a*a%p;
	}
	return res;
}
int main(){
	scanf("%d%d",&n,&k);inv[1]=1;
	for(int i=2;i<=n;++i){
		ll x=-mod/i*inv[mod%i]%mod;
		x=(x+mod)%mod;inv[i]=x;
	}jie[1]=njie[1]=1;jie[0]=njie[0]=1;
	for(int i=2;i<=n;++i) jie[i]=jie[i-1]*i%mod,njie[i]=njie[i-1]*inv[i]%mod;
	for(int i=0;i<=n;++i){
		ll c=jie[n]*njie[i]%mod*njie[n-i]%mod;
		ll temp=power(2,n-i,mod-1);
		f[i]=c*(power(2,temp,mod)-1)%mod;
		f[i]=(f[i]+mod)%mod;
	}
	for(int i=k;i<=n;++i){
		ll c=jie[i]*njie[k]%mod*njie[i-k]%mod;
		c=c*f[i]%mod;
		if((i-k)%2) ans=(ans-c)%mod,ans=(ans+mod)%mod;
		else ans=(ans+c)%mod;
	}
	cout<<ans<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39759315/article/details/88849195