[2018國家集訓隊][UOJ449] 喂鴿子 [dp+組合數學]

題面

傳送門ios

思路

首先,這道題是能夠暴力min-max反演+NTT作出來的......可是這個不美觀,我來說一個作起來舒服一點的作法git

一個很是basic的idea:咱們發如今一隻鴿子吃飽之後再餵給它的玉米都是「無效」的,而且咱們如此認爲,那麼有效的玉米數量是肯定的:$nk$ide

吃飽序列和投喂序列

那麼,咱們考慮一個序列$r_i$,表示第$i$次喂完玉米以前,有多少隻鴿子是吃飽的,咱們稱之爲吃飽序列idea

注意到本題中每隻鴿子互不相同,所以咱們再肯定一個「有效喂鴿子操做」的序列,咱們稱之爲投喂序列spa

特別注意:吃飽序列的構造雖然部分依賴於投喂序列,可是投喂序列的出現機率是徹底依賴於吃飽序列的code

顯然,對於一組操做序列和吃飽序列,咱們能夠獲得這組序列出現的總機率:$Prob=\prod_{i=1}^{nk}P_{r_i}$get

其中$P_i$表示吃飽了$i$個的狀況下,下一個投喂選到咱們的目標鴿子的機率string

那麼咱們如今實際上把投喂序列變成了能夠由吃飽序列求出來,而吃飽序列的下一項又反過來由投喂序列肯定,這麼一個狀況it

咱們若是要考慮總貢獻,咱們發現還須要考慮最終成功完成一次有效投喂(注意由於前面算的是機率,這裏只要隨便投喂一個沒吃飽的鴿子就能夠了)的指望時間io

這個時間$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';
}
相關文章
相關標籤/搜索