《算法竞赛进阶指南》0x37容斥原理和Mobius函数 Devu和鲜花

题目链接:https://www.acwing.com/problem/content/216/

给出一个多重集,要求从里面取出m个,并且这m个构成的多重集不能是重复的,问有多少个这样的多重集。

如果取出的数不大于任意一个同类集合的元素个数的话,直接通过C(n+m-1,n-1)即可求出取出的非重复的多重集数量,但是此时的m是很多大的,所以需要通过容斥原理枚举2^n个数来进行判断。统计一个数的二进制中1的个数和对应的1的位,就可以知道属于哪个容斥子元素以及其正负情况,注意特判一下等于0的情况,求阶乘的时候,可以运用lucas定理和逆元。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
const int mod = 1000000007;
typedef long long ll;
const int maxn = 25;
ll a[maxn];
ll m;
ll inv[25];
ll ksm(ll a,ll b){
    ll ans=1;
    b%=(mod-1);
    while(b){
        if(b&1)ans=(ans*a)%mod;
        b>>=1;
        a=(a*a)%mod;
    }
    return ans;
}
ll C(ll y,ll x){//计算组合数C(y,x) 
    if(y<0 || x<0 || y<x)return 0;
    if(x==0 || y==0 || x==y)return 1;
    y%=mod;//lucas定理
    ll ans=1;
    for(int i=1;i<=x;i++){
        ans=ans*(y-x+i)%mod*inv[i]%mod;
    } 
    return ans;
}
int main(){
    for(int i=1;i<=20;i++)inv[i]=ksm(i,mod-2);
    ll ans=0;
    ll n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int x=0;x<1<<n;x++){
        if(x==0){
            ans=C(n+m-1,n-1)%mod;
        }else{
            ll t=n+m;
            int p=0;//计算集合的数量,为了判断容斥原理中的正负 
            for(int i=0;i<n;i++){//扫描n个集合,看是否包括在内 
                if(x>>i & 1){
                    t-=a[i+1];
                    p++;
                }
            }
            t-=(p+1);
            if(p&1)ans=(ans-C(t,n-1))%mod; 
            else ans=(ans+C(t,n-1))%mod; 
        }
    }
    cout<<(ans+mod)%mod<<endl;
    return 0;
}

 

posted @ 2020-07-10 21:04  WA自动机~  阅读(387)  评论(0编辑  收藏  举报