bzoj 2839: 集合计数 (容斥原理)

题目描述

传送门

题目大意:
一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数

题解

首先我们 考虑确认出交集中的K个元素,选择的方案数是 C(n,k) .那么剩下的元素共 nk 个,剩下的元素构成的子集是 2nk 我们可以从这些子集中任意选择,然后与选择出的元素拼接到一起形成合法的子集。选择的方案就是 2nki=1C(2nk,i) 发下这个式子就是杨辉三角的第 2nk+1 行减去 C(2nk,0) ,有公式可知,杨辉三角的第i行的和为 2i1 ,所以选择的方案就是 22nk1 ,由欧拉定理 aϕ(n)=1mod n ,所以我们可以利用快速幂求解。
但是我们选取出的子集的交集至少为k的,那么需要容斥原理来计算。根据找规律的容斥的系数是 C(i,k) .

代码

#include
#include
#include
#include
#include
#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<printf("%lld\n",(ans%p+p)%p);
}


你可能感兴趣的:(容斥原理)