集合计数,容斥原理

一个有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;
}

```
posted @ 2021-04-13 17:10  WindZR  阅读(240)  评论(0编辑  收藏  举报