AcWing 214. Devu和鲜花 (容斥)打卡

时间:2021-05-05 18:59:48

Devu有N个盒子,第i个盒子中有AiAi枝花。

同一个盒子内的花颜色相同,不同盒子内的花颜色不同。

Devu要从这些盒子中选出M枝花组成一束,求共有多少种方案。

若两束花每种颜色的花的数量都相同,则认为这两束花是相同的方案。

结果需对109+7109+7取模之后方可输出。

输入格式

第一行包含两个整数N和M。

第二行包含N个空格隔开的整数,表示A1,A2,,ANA1,A2,…,AN。

输出格式

输出一个整数,表示方案数量对109+7109+7取模后的结果。

数据范围

1N201≤N≤20,
0M10140≤M≤1014,
0Ai10120≤Ai≤1012

输入样例:

3 5
1 3 2

输出样例:

3


题意:让你在n朵花里面选择m朵,其中有些花朵相同,然后求方案数有多少种,不用顺序排列
思路:这里其实就是一个多重集的组合数问题

多重集的排列问题

有k个物品,总共有n个种类,第ni个种类有a[i]个物品,问全排列数是多少
就是 n!/(n1!*n2!*....ni!),道理很简单,全部不相同的全排列数是n!,但是这个其中有重复,所以我们需要去重,所有我们对每个种类求个全排列
然后除就行,其中每个种类的重复又可以和其他种类的重复产生联系,所以是乘法原理,把所有的种类全排列相乘即可
多重集的组合问题
有k个物品,总共有n个种类,第ni个种类有a[i]个物品,然后从中选择m个,问有多少个选择方法

多重集的组合数的弱化版(m=<a[i])
如果是基于这个条件的话,那么我们就可以直接采用隔板法来计算,首先我们选择m个商品,我们条件那么就只有在每个种类选择的个数加起来等于m即可
又因为我们的m不会大于每个种类个数,那么我们就把他分为n组即可,使用n-1个隔板,那么这个的答案就是
C(n+m-1,n-1) 我们把我们的m个商品分个类,分成n份,所有我们还加入了n-1个隔板,所以就是这个答案

多重集的组合数的强化版(m<=n)
首先这个不能和上面用一样的方法了,上面的我们限制了,m<=a[i],说明我们无论怎么对m进行分割,单个分组的个数不可能大于输入的组的个数,但是现在给定你的那个条件有可能出现
单个分组大于输入分组个数,这样的方案都是不可行的,在这里我们就需要使用容斥来去掉我们无法使用的方案,这里我们可以使用原来的方案去减掉有一个超过限制,两个,三个.....n个
然后这1,2,3,....n个中又有重复,所以我们需要使用容斥
Ck1kr1i=1kCk1k+rni2+1ijnCk1k+rninj3...+(1)kCk1k+rki=1ni(k+1)
写代码的时候我们枚举所有情况,我们使用状压,当前二进制1就代表容斥选择了这个项


#include<bits/stdc++.h>
#define maxn 100005
#define mod 1000000007
using namespace std;
typedef long long ll;
ll n,m;
ll a[50];
ll inv[50];
ll quick_pow(ll a,ll b)
{
    ll ans=1;
    while(b){
        if(b&1) ans=(ans*a)%mod;
        b=b/2;
        a=(a*a)%mod;
    }
    return ans;
}
ll C(ll n,ll m){
    if(n<0||m<0||n<m) return 0;
    n%=mod;
    if(n==0||m==0) return 1;
    ll ans=1;
    for(int i=0;i<m;i++){
        ans=(ans*(n-i))%mod;
    }
    for(int i=1;i<=m;i++){
        ans=(ans*inv[i])%mod;
    }
    return ans;
} 
int main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) inv[i]=quick_pow(i,mod-2);//求出1-n的逆元,留着求组合数乘积的时候使用
    ll ans=0;
    for(int i=0;i<(1<<n);i++){
        if(i==0){
            ans=(ans+C(n+m-1,n-1))%mod;
        }
        else{
            ll p=0;
            ll t=n+m;
            for(int j=0;j<n;j++){
                if((i>>j)&1){
                    p++;
                    t-=a[j+1];
                }
            } 
            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; 
}