「CF451E」題解
阿新 • • 發佈:2021-11-18
Description
有限制多重集組合模板題。
可轉化題目
有 \(n\) 種盒子,每個盒子可以放的小球數最多為 \(f_i\),盒子可為空,求總共放 \(s\) 個球的方案數,答案對 1e9+7 取模。
發現直接求方案不好做,但是總方案和不合法的方案都容易算出,考慮容斥。
總方案十分經典,可以考慮成 \(s\) 個小球,\(n-1\) 塊隔板,隔板可相鄰(某個盒子為空),問隔開的方案。
那麼因為隔板可以相鄰,直接把隔板當成小球考慮,選出 \(n-1\) 個小球的方案,即\(\binom{s+n-1}{n-1}\)。
到這一步可以看出組合數的下項非常大但上項極小,使用一個常用的技巧,即把上下項約掉一部分後再分開運算。
即
前一項只用運算 \(n-1\) 次,後一項用逆元預處理可 \(\Theta(1)\) 求得,所以單次求組合數時間複雜度為 \(\Theta(n)\)。
解決了組合數的問題,接下來就是很 naive 的容斥了,設 \(\operatorname{G[i]}\) 表示第 \(i\) 個盒子不合法的方案,\(S\) 表示總方案,則
在列舉每個盒子不合法情況時顯然不能直接球的個數,但是我們知道 \(f_i+1\)
那麼我們直接把這些球放進第 \(i\) 個盒子,剩下的球繼續分的都是不合法的方案。
即 \[G[i]=\binom{s-f_i+n-1}{n+1} \]
多個盒子時同理。
Code:
#include<cstdio>//直接轉化為球相同+隔板,且隔板的數量極少 #include<iostream>//列舉不合法時先控制成至少,因為剩下的隔板可以繼續分配,然後相當於球減少了(f_i+1)個繼續用求全集公式 using namespace std;//然後就變成了一個小技巧,當下項極大時拆分一下即可 const int Mod=1e9+7; const int MAXN=25; const int M=20; #define ll long long int n,o[2]; ll f[MAXN],s,fra[MAXN],ofra[MAXN],Ans; ll ksm(ll a,int b) { ll ans=1; while(b){ if(b&1) ans=ans*a%Mod; a=a*a%Mod;b>>=1; } return ans; } ll C(ll n,int k){ if(n<k) return 0; ll ans=1; for(ll i=n;i>n-k;i--){ ans=ans*(i%Mod)%Mod; } return ans*ofra[k]%Mod; } int main(){ o[0]=1,o[1]=-1; fra[0]=1; for(int i=1;i<=M;i++){ fra[i]=fra[i-1]*i%Mod; } ofra[M]=ksm(fra[M],Mod-2); for(int i=M;i;i--){ ofra[i-1]=ofra[i]*i%Mod; } scanf("%d%lld",&n,&s); for(int i=1;i<=n;i++){ scanf("%lld",&f[i]); } Ans=C(s+n-1,n-1); int k=1<<n; for(int i=1;i<k;i++){ int ty=0;ll tot=0; for(int j=0;j<n;j++){ ty+=(i>>j)&1; if((i>>j)&1) tot+=f[j+1]+1; } Ans=(Ans+o[ty%2]*C(s+n-1-tot,n-1)%Mod)%Mod; Ans=(Ans+Mod)%Mod; } printf("%lld",Ans); return 0; }
綜合來看這就是用了一個小技巧和有個小細節的容斥裸題 qwq。
\(\Bbb{End.}\)
\(\Bbb{Thanks} \space \Bbb{For} \space \Bbb{Reading.}\)