传送门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}$
上式的两个部分分别表明每个投喂序列出现的几率,以及这个吃饱序列的指望完成时间
这个东西很差处理,由于咱们没有办法直接知道每次成功投喂之后会不会使吃饱序列的下一项+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]$的转移,只须要在上面的转移的基础上乘上上述组合系数便可
若仍有疑问,能够参见代码的实现
#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'; }