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<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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值