「BZOJ2839」集合计数

「BZOJ2839」集合计数

题目大意:

一个包含 (n) 个数的集合有 (2^n) 个子集,从这些子集中取出若干个集合(至少一个),使他们的交集的元素个数恰好为 (k),求方案数,答案对 (1e9+7) 取模。


首先考虑一个很直观的思路:我们钦定 (k) 个数是他们的交集,则这样的方案数为 (inom{n}{k}) ,同时,包含这 (k) 个数的集合个数为 (2^{n-k}) ,每个集合有选与不选两个状态,但依据题意,不能够全部不选,所以这样得到的总方案数 (b_k)

[b_k=inom{n}{k}(2^{2^{n-k}}-1) ]

但这样求出来的结果并不是我们想要的,设这些集合真实的交集集合 (j) 个数组成的集合为 (A),钦定的 (k) 个数组成的集合为 (B) ,则当(B subseteq A) 时, 那么这个方案就会被统计一次,总共就会被统计 (inom j k) 次。

设交集中恰好有 (k) 个元素的方案数为 (a_k),则有

[b_k=sum_{i=k}^n inom i k a_i ]

然后这里,我们可以利用容斥原理来推出,但更方便的是使用二项式反演,即

[f(k)=sum_{i=k}^n inom i k g(i) iff g(k)=sum_{i=k}^n (-1)^{i-k} inom i k f(i) ]

这个式子可以通过直接将前式代入得到。

同样,二项式反演也还有另一种形式

[f(n)=sum_{i=k}^n inom n i g(i) iff g(n)=sum_{i=k}^n (-1)^{n-i}inom n i f(i) ]

证明方法类似,在此不作赘述。

关于这道题,我们直接反演一下即可得到答案,即

[a_k=sum_{i=k}^n (-1)^{i-k}inom i k inom n k(2^{2^{n-k}}-1) ]

时间复杂度为 (O(n))

( exttt{Code:})

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const ll p=1e9+7;
const ll maxn=1e6+5;
ll ksm(ll a,ll b,ll p){
	ll ans=1;
	for(;b;b>>=1,a=1ll*a*a%p)
		if(b&1) ans=1ll*ans*a%p;
	return ans;
}
ll fac[maxn],inv[maxn];
ll C(ll n,ll m){
	if(n<m) return 0;
	return 1ll*fac[n]*inv[m]%p*inv[n-m]%p;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	ll n,k;
	cin>>n>>k;
	fac[0]=1;
	for(ll i=1;i<=n;++i) fac[i]=1ll*fac[i-1]*i%p;
	inv[n]=ksm(fac[n],p-2,p);
	for(ll i=n-1;i>=0;--i) inv[i]=1ll*inv[i+1]*(i+1)%p;
	ll ans=0;
	for(ll i=k;i<=n;++i){
		ans=(ans+1ll*((i-k)&1?(-1):(1))*(C(i,k)*C(n,i)%p*(ksm(2,ksm(2,n-i,p-1),p)%p-1+p))%p+p)%p;
	}
	cout<<ans<<'
';
	return 0;
}

原文地址:https://www.cnblogs.com/HenryHuang-Never-Settle/p/11707108.html