[2018国家集训队][UOJ449] 喂鸽子 [dp+组合数学]
題面
傳送門
思路
首先,這道題是可以暴力min-max反演+NTT做出來的......但是這個不美觀,我來講一個做起來舒服一點的做法
一個非常basic的idea:我們發現在一只鴿子吃飽以后再喂給它的玉米都是“無效”的,并且我們如此認為,那么有效的玉米數量是確定的:$nk$
吃飽序列和投喂序列
那么,我們考慮一個序列$r_i$,表示第$i$次喂完玉米之前,有多少只鴿子是吃飽的,我們稱之為吃飽序列
注意到本題中每只鴿子互不相同,因此我們再確定一個“有效喂鴿子操作”的序列,我們稱之為投喂序列
特別注意:吃飽序列的構造雖然部分依賴于投喂序列,但是投喂序列的出現概率是完全依賴于吃飽序列的
顯然,對于一組操作序列和吃飽序列,我們可以得到這組序列出現的總概率:$Prob=\prod_{i=1}^{nk}P_{r_i}$
其中$P_i$表示吃飽了$i$個的情況下,下一個投喂選到我們的目標鴿子的概率
那么我們現在實際上把投喂序列變成了可以由吃飽序列求出來,而吃飽序列的下一項又反過來由投喂序列確定,這么一個情況
我們如果要考慮總貢獻,我們發現還需要考慮最終成功完成一次有效投喂(注意因為前面算的是概率,這里只要隨便投喂一個沒吃飽的鴿子就可以了)的期望時間
這個時間$T_i=E_{r_i}$,其中$E_i=\frac{n}{n-i}$,這里表示在$i$個鴿子吃飽的前提下的有效投喂期望時間
那么,我們可以得到我們在確定了一個吃飽序列和對應的投喂序列時最終答案的表達式:$Ans=\prod_{i=1}^{nk}P_{r_i}\sum_{i=1}^{nk} E_{r_i}$
上式的兩個部分分別代表每一個投喂序列出現的概率,以及這個吃飽序列的期望完成時間
轉化為DP
這個東西不好處理,因為我們沒有辦法直接知道每次成功投喂以后會不會使吃飽序列的下一項+1(也就是有一只鴿子吃飽了)
注意到貢獻都只和$r_i$有關系,而和目前沒吃飽的鴿子吃掉的玉米的分配沒有關系!
所以我們大可以隨意分配這些沒吃飽的鴿子吃掉的玉米,下文中簡稱為白玉米
那么我們可以基于上面的表達式得到一個$dp$的做法:
設$f[i][j]$表示投喂了$i$次,有$j$個鴿子吃飽了的總貢獻,$g[i][j]$則表示上述情況出現的概率(也就是只考慮表達式中含$P_{r_i}$的部分)
那么我們把表達式轉化一下:
$f[i][j]=\sum_{\lbrace r\rbrace} \prod_{x=1}^{i}P_{r_x}\sum_{y=1}^{i} E_{r_y}$
$f[i][j]=\sum_{\lbrace r\rbrace} (P_j\prod_{x=1}^{i-1}P_{r_x})(E_j+\sum_{y=1}^{i-1} E_{r_y})$
$f[i][j]=P_j\ast(\sum_{\lbrace r\rbrace} \prod_{x=1}^{i-1}P_{r_x}\sum_{y=1}^{i-1} E_{r_y})+P_j\ast E_j\ast(\sum_{\lbrace r\rbrace} \prod_{x=1}^{i-1}P_{r_x})$
$f[i][j]=P_jf[i-1][j]+P_jE_jg[i-1][j]$
這樣我們就完成了沒有新鴿子吃飽的情況下的$f[i][j]$的轉移
那么對于$g[i][j]$的轉移,很顯然是$g[i][j]=P_jg[i-1][j]$,不再贅述
對于新加入的玉米使得一只鴿子吃飽的情況,我們需要對目前存在的白玉米進行染色,此時染色的方案數顯然為$\binom{i-jk}{k-1}$
所以對于從$f[i][j]$到$f[i+1][j+1]$的轉移,只需要在上面的轉移的基礎上乘上上述組合系數即可
若仍有疑問,可以參見代碼的實現
Code
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cassert> #define MOD 998244353 #define ll long long using namespace std; inline int read(){int re=0,flag=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-') flag=-1;ch=getchar();}while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();return re*flag; } inline int add(int a,int b){a+=b;if(a>=MOD) a-=MOD;return a; } inline void addd(int &a,int b){a+=b;if(a>=MOD) a-=MOD; } inline int qpow(int a,int b){int re=1;while(b){if(b&1) re=1ll*re*a%MOD;a=1ll*a*a%MOD;b>>=1;}return re; } int n,m,fac[1000010],finv[1000010],inv[1000010]; inline void init(){int i,len=1000000;fac[0]=fac[1]=finv[0]=finv[1]=inv[1]=1;for(i=2;i<=len;i++) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;for(i=2;i<=len;i++) fac[i]=1ll*fac[i-1]*i%MOD;finv[len]=qpow(fac[len],MOD-2);for(i=len;i>2;i--) finv[i-1]=1ll*finv[i]*i%MOD; } inline int C(int x,int y){if(x<0||y<0||x<y) return 0;return 1ll*fac[x]*finv[y]%MOD*finv[x-y]%MOD; } int f[100010][110],g[100010][110],p[100010],e[100010]; int main(){n=read();m=read();int i,j,tf,tg,tt;init();for(i=0;i<=n;i++) p[i]=inv[n-i],e[i]=1ll*n*inv[n-i]%MOD;f[0][0]=0;g[0][0]=1;for(i=0;i<n*m;i++){for(j=0;j*m<=i;j++){tg=1ll*g[i][j]*p[j]%MOD;tf=add(1ll*f[i][j]*p[j]%MOD,1ll*p[j]*e[j]%MOD*g[i][j]%MOD);tt=C(i-j*m,m-1);addd(f[i+1][j],tf);addd(g[i+1][j],tg);addd(f[i+1][j+1],1ll*tf*tt%MOD);addd(g[i+1][j+1],1ll*tg*tt%MOD);}} // for(i=0;i<=n*m;i++) for(j=0;j*m<=i;j++) cout<<i<<' '<<j<<' '<<f[i][j]<<' '<<g[i][j]<<'\n';cout<<(1ll*fac[n]*f[n*m][n]%MOD)<<'\n'; }轉載于:https://www.cnblogs.com/dedicatus545/p/10706279.html
總結
以上是生活随笔為你收集整理的[2018国家集训队][UOJ449] 喂鸽子 [dp+组合数学]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022年10大最流行的数据分析工具,掌
- 下一篇: 7-1 换硬币 (20 分)