[BZOJ2839]集合计数(容斥原理+组合数学)

题目描述

传送门

题解

首先考虑固定k个元素,方案为 Ckn
还剩下 2nk 个集合,可以任选若干个集合 C12nk+C22nk+..+C2nk2nk=22nk
但是这样选出来的有可能有不合法的,交集大小可能大于k,所以要减去k+1,加上k+2…
就是个容斥了
f(k)=Ckn(22nk)
那么答案应该为 f(k)Ckkf(k+1)Ckk+1+f(k+2)Ckk+2...f(n)Ckn
容斥系数我刚开始是找的规律,但是后来发现,因为第一项即使不考虑交集大小可以大于k的情况,也会多出来很多重复的情况,因为选取的k个元素和选取的集合可能是重复的。那么后面进行容斥的时候就要利用组合数将重复的去掉
对于 22nk 的求法,可以变成 22nk mod φ(p) 。根据欧拉定理 aφ(p)1(modp),(a,p)=1 ,可以得出 anφ(p)an(modp) ,而这里的a和p显然是互质的。
这题如果直接暴力的话会非常慢。比较科学的做法是线性推出逆元然后再预处理阶乘和逆元的阶乘,以及2的整数次幂。这样速度就比较优秀了
线性推逆元方法 inv(i)=(pp/i)inv(p%i)%p ,初始 inv(1)=1

代码

#include
#include
#include
#include
#include
using namespace std;
#define Mod 1000000007
#define N 1000005
#define LL long long

int n,k,f;
LL mi[N],mul[N],inv[N],ans;

void calc(int n)
{
    mul[0]=1;
    for (int i=1;i<=n;++i) mul[i]=mul[i-1]*(LL)i%Mod;
    inv[1]=1;
    for (int i=2;i<=n;++i) inv[i]=inv[Mod%i]*(Mod-Mod/i)%Mod;
    inv[0]=1;
    for (int i=1;i<=n;++i) inv[i]=inv[i]*inv[i-1]%Mod;
    mi[0]=1;
    for (int i=1;i<=n;++i) mi[i]=mi[i-1]*(LL)2%(Mod-1);
}
LL fast_pow(LL a,int p,int mod)
{
    LL ans=1;
    for (;p;p>>=1,a=a*a%mod)
        if (p&1)
            ans=ans*a%mod;
    return ans;
}
LL C(int n,int m)
{
    if (m>n) return 0;
    return mul[n]*inv[m]%Mod*inv[n-m]%Mod;
}
int main()
{
    scanf("%d%d",&n,&k);
    calc(n);
    f=1;
    for (int i=k;i<=n;++i,f=-f)
        ans+=C(n,i)*(fast_pow(2,mi[n-i],Mod)-1)%Mod*C(i,k)%Mod*f;
    ans=(ans%Mod+Mod)%Mod;
    printf("%lld\n",ans);
}


你可能感兴趣的:(题解,容斥原理,组合数学)