《算法竞赛进阶指南》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; }
每一个不曾起舞的日子,都是对生命的辜负。