zoukankan      html  css  js  c++  java
  • AcWing 214. Devu和鲜花 (容斥)打卡

    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; 
    } 
  • 相关阅读:
    Javascript Promise对象学习
    JavaScript 判断一个对象的数据类型。
    Angular SEO方案
    【GOF23设计模式】迭代器模式
    【GOF23设计模式】责任链模式
    【GOF23设计模式】享元模式
    【GOF23设计模式】外观模式
    【GOF23设计模式】装饰模式
    【GOF23设计模式】组合模式
    【GOF23设计模式】桥接模式
  • 原文地址:https://www.cnblogs.com/Lis-/p/11172776.html
Copyright © 2011-2022 走看看