集合计数(容斥原理)
题目大意
一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数,答案模1000000007.
题解
首先很容易想到从n个元素中选出k个作为交集的方案数 C(n,k)
以下是错误思路,想偏了……(-_-)!
然后对于每个k在剩余的n-k个元素中任选元素,与k共同组成一个集合,共有2^(n-k)种集合,这些个集合,又有2^(2^(n-k))中方案使得交集为k
然后……就会发现有对于不同的k的方案有一堆重复(容斥啊!)……不知道咋容斥
正解
从剩下的n-k个元素中选元素,组成没有交集的那么多个集合,使得他们的交集只能是k
发现上面错解里面有一些方案中的交集是k+1个元素,那么减去这些个C(n-k,1)*2^(2^(n-k-1)),这样又会多减去好几次交集为k+2的方案,再加上,
怎样容斥就出来了
即对于每个k而言,加上k个交集的方案,减去k+1个交集的方案,加上k+2个交集的方案,……加上(-1)^(n-k)*(n个交集的方案)
干成一块,枚举交集i,同时不能为空集(即不能一个都不选),2^(2^(n-i))要减去全都不选的情况: ans+=(-1)^(i-k)*C(n,i)*(2^(2^(n-i))-1)*C(i,k);
一开始我的代码濒临TLE
然后学了一手线性求逆元,代码如下:
jc[0]=1; for(int i=1;i<=n;i++) jc[i]=(long long)(jc[i-1]*i)%mod; inv[n]=pow(jc[n],mod-2); for(int i=n-1;i>=0;i--) inv[i]=((inv[i+1]%mod)*(i+1)%mod)%mod;
经过我的不断修改,我终于发现了30到AC的秘密
ans+=(C(i,n)*((1ll*(pow(2,q[n-i])-1)*C(k,i)*1ll)%mod*ha*1ll))%mod; 我没写那个括号(玄学问题,我也不知道,有大佬知道的话,麻烦告诉一声,lockey在此%)
AC代码
#include<iostream> #include<cstdio> using namespace std; const long long mod=1000000007; int n,k; long long q[1100000],jc[1100000],inv[1100000]; long long ans=0; long long pow(long long a,long long b){ long long ans=1; a%=mod; while(b){ if(b&1) ans=(long long)(ans*a)%mod; b>>=1; a=(long long)(a*a)%mod; } return ans%mod; } long long C(long long m,long long n){ if(!m||m==n) return 1; if(m>n) return 0; if(m<mod&&n<=mod) return ((long long)((long long)(jc[n]*inv[m])%mod)*inv[n-m])%mod; return ((long long)C(m%mod,n%mod)*C(m/mod,n/mod))%mod; } int main(){ scanf("%d%d",&n,&k); jc[0]=1,q[0]=1; for(int i=1;i<=n;i++) jc[i]=(long long)(jc[i-1]*i)%mod; inv[n]=pow(jc[n],mod-2); for(int i=n-1;i>=0;i--) inv[i]=((inv[i+1]%mod)*(i+1)%mod)%mod; for(int i=1;i<=n;i++) q[i]=(q[i-1]*2)%(mod-1); long long ha=1; for(int i=k;i<=n;i++,ha=-ha){ ans+=(C(i,n)*((1ll*(pow(2,q[n-i])-1)*C(k,i)*1ll)%mod*ha*1ll))%mod; } cout<<1ll*(1ll*(ans+mod)%mod+mod)%mod<<endl; }