题目描述
传送门
题目大意:
一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数
题解
首先我们 考虑确认出交集中的K个元素,选择的方案数是
C(n,k)
.那么剩下的元素共
n−k
个,剩下的元素构成的子集是
2n−k
我们可以从这些子集中任意选择,然后与选择出的元素拼接到一起形成合法的子集。选择的方案就是
∑2n−ki=1C(2n−k,i)
发下这个式子就是杨辉三角的第
2n−k+1
行减去
C(2n−k,0)
,有公式可知,杨辉三角的第i行的和为
2i−1
,所以选择的方案就是
22n−k−1
,由欧拉定理
aϕ(n)=1mod n
,所以我们可以利用快速幂求解。
但是我们选取出的子集的交集至少为k的,那么需要容斥原理来计算。根据找规律的容斥的系数是
C(i,k)
.
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define p 1000000007
#define LL long long
#define N 1000003
using namespace std;
LL jc[N],inv[N],mi[N*3];
int n,k;
LL quickpow(LL num,int x,LL p1)
{
if (x<=3000000&&p1==p) return mi[x];
LL base=num%p; LL ans=1;
while (x) {
if (x&1) ans=ans*base%p1;
x>>=1;
base=base*base%p1;
}
return ans;
}
LL C(int n,int m)
{
return jc[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
scanf("%d%d",&n,&k);
jc[0]=1; mi[0]=1;
for (int i=1;i<=n;i++) jc[i]=jc[i-1]*i%p;
for (int i=0;i<=n;i++) inv[i]=quickpow(jc[i],p-2,p);
for (int i=1;i<=3000000;i++) mi[i]=mi[i-1]*2%p;
LL ans=0; int t;
for (int i=k;i<=n;i++) {
if ((i-k)&1) t=-1;
else t=1;
LL cnt=quickpow(2,n-i,p-1);
ans+=C(n,i)*C(i,k)%p*(quickpow(2,cnt,p)-1)%p*t;
ans%=p;
//cout<<ans<<endl;
}
printf("%lld\n",(ans%p+p)%p);
}