集合计数,容斥原理
一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数,答案模1000000007。(是质数喔~)
输入格式
一行两个整数N,K
输出格式
一行为答案。
样例
样例输入
3 2
样例输出
6
析:要求交集个数为K,直接求显然不好做,考虑用容斥原理,交集个数为K=交集至少为K-交集至少为(K+1)+交集至少为(K+2)—......
若交集至少为K,则取出K个元素,还剩下(N_K)个元素,剩下的元素共有2^(n-k)-1种集合(因为不能是空集),把每个集合看成一种元素,则共有2^(2^((n-k)-1))种选择方案,则方案数为C(n,k)*2^(2^((n-k)-1)),以此类推
注意,若交集至少为i,则重复的方案数为C(i,k)!!!
知识点:lucas定理:C(n,m)%p=C(n/p,m/p)*C(n%p,m%p);
C(n,m)=c!*inv[m!]*inv[(n-m)!]
线性求阶乘逆元:inv[i]=(inv[i+1]%mo*(i+1)%mo)%mo,因为逆元相当于求倒数,1/(n+1)!*(n+1)=n!;
A^x = A^(x % Phi(C) + Phi(C)) (mod C)
代码如下:
```cpp
#include<bits/stdc++.h>
#define ll long long
#define re register int
#define mo 1000000007
#define N 20000010
#define phi 1000000006
using namespace std;
ll n,k,ans;
ll jc[N],f[N],inv[N];
bool p;
ll ksm(ll d,ll z)
{
ll out=1;
while(z)
{
if(z&1)
out=out*d%mo;
d=d*d%mo;
z>>=1;
}
return out%mo;
}
ll suan(ll n,ll m)
{
return jc[n]%mo*inv[m]%mo*inv[n-m]%mo;
}
ll lucas(ll n,ll m)
{
if(m>n)
return 0;
if(n==0||m==0)
return 1;
return lucas(n/mo,m/mo)*suan(n%mo,m%mo)%mo;
}
int main()
{
scanf("%lld%lld",&n,&k);
jc[0]=1;
for(re i=1;i<=n;i++)
jc[i]=jc[i-1]*i%mo;
f[0]=1;
for(re i=1;i<=n;i++)
f[i]=(f[i-1]*2%phi+phi)%phi;
inv[n]=ksm(jc[n],mo-2);
for(re i=n-1;i>=0;i--)
inv[i]=(inv[i+1]%mo*(i+1)%mo)%mo;
for(re i=k;i<=n;i++)
{
if(p==0)
{
ans=(ans+lucas(i,k)%mo*lucas(n,i)%mo*(ksm(2,f[n-i])-1)%mo)%mo;
p=1;
}
else
{
ans=(ans-lucas(i,k)%mo*lucas(n,i)%mo*(ksm(2,f[n-i])-1)%mo)%mo;
p=0;
}
}
printf("%lld",(ans+mo)%mo);
return 0;
}
```