题解 通用测评号
Description
完成清扫银河计划带来的信心并不能让跳蚤们的航天科技突飞猛进,你看不到任何用现有的工质发动机技术完成环银河系航行的可能性。但这时,章北蚤向你展示了最新的通用测评号恒星级宇宙飞船 —— 它拥有最新一代的工质发动机,全功率推进时,理论上可以加速到光速的千分之五。
为了实验通用测评号的实际效果,你被安排给通用测评号装填燃料。通用测评号上有 𝑛 个燃料舱,初始时均为空。一个燃料舱被填满时可以储藏 𝑎 单位的燃料。但为了完成本次实验,只需要每个燃料舱装填至少 𝑏 单位的燃料即可。
装填燃料是个非常无聊的事情,因此你每次会等概率随机选一个没有满的燃料舱,并且在其中放入 1 单位的燃料,直至所有燃料舱均包含至少 𝑏 单位的燃料。
但是由于设计上的失误,一个燃料舱被填满 𝑎 单位的燃料后,该燃料舱与发动机连接的管道由于压力过大会坏掉。章北蚤发现了这个问题,他想估计坏掉的管道数量,所以想请你算一算期望有多少个燃料舱被填满了。
为了避免实数计算带来的浮点误差,你只需要输出答案对 998244353 取模后的结果。
Solution
可以想到的是,答案就是 \(1\) 会装满的概率乘上 \(n\) 。
考虑如何求这个概率。可以发现的是,这个即为在仍有位置 \(<b\) 时 \(1\) 已经变为 \(a\) 的概率。考虑转化,变为一个序列,序列里的每个元素表示每次选了哪个位置,可以考虑钦定 \(p\) 个元素出现了,然后进行容斥,系数就是 \((-1)^p\binom{n-1}{p}\)。
那么设 \(F(x)=\sum_{i=0}^{b-1} \frac{x_i}{i!}\) ,那么对于钦定选了 \(p\) 个的时候,贡献就可以通过枚举 \(1\) 出现的最后一个位置进行计算:
这个时候可以直接暴力计算 \(F^p(x)\),不过也可以递推。具体来说,我们发现:
然后就可以 \(\Theta(n^3)\) 递推了。
Code
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define mod 998244353
#define MAXN 255
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> inline void chkmax (T &a,T b){a = max (a,b);}
template <typename T> inline void chkmin (T &a,T b){a = min (a,b);}
int n,a,b,up = 62500,f[MAXN * MAXN],g[MAXN * MAXN],fac[MAXN * MAXN],ifac[MAXN * MAXN];
int mul (int a,int b){return 1ll * a * b % mod;}
int dec (int a,int b){return a >= b ? a - b : a + mod - b;}
int add (int a,int b){return a + b >= mod ? a + b - mod : a + b;}
int qkpow (int a,int b){
int res = 1;for (;b;b >>= 1,a = mul (a,a)) if (b & 1) res = mul (res,a);
return res;
}
void Sub (int &a,int b){a = dec (a,b);}
void Add (int &a,int b){a = add (a,b);}
int getinv (int x){return mul (ifac[x],fac[x - 1]);}
int binom (int a,int b){return a >= b ? mul (fac[a],mul (ifac[b],ifac[a - b])) : 0;}
signed main(){
read (n,a,b);
fac[0] = 1;for (Int i = 1;i <= up;++ i) fac[i] = mul (fac[i - 1],i);
ifac[up] = qkpow (fac[up],mod - 2);for (Int i = up;i;-- i) ifac[i - 1] = mul (ifac[i],i);
int ans = 0;
for (Int i = 0;i < b;++ i) f[i] = ifac[i];
for (Int p = 1;p <= n - 1;++ p){
int now = 0;
for (Int j = 0,tmp = qkpow (qkpow (1 + p,a),mod - 2);j <= p * (b - 1);++ j,tmp = mul (tmp,getinv (1 + p)))
Add (now,mul (f[j],mul (fac[a + j - 1],tmp)));
now = mul (now,mul (binom (n - 1,p),ifac[a - 1]));
ans = p & 1 ? add (ans,now) : dec (ans,now);
int c = mul (p + 1,ifac[b - 1]);
for (Int j = 0;j <= p * (b - 1);++ j) f[j] = mul (f[j],c);
for (Int j = (p + 1) * (b - 1);j >= b - 1;-- j) f[j] = f[j - (b - 1)];
for (Int j = b - 2;~j;-- j) f[j] = 0;
g[0] = 1;for (Int j = 1;j <= (p + 1) * (b - 1);++ j)
g[j] = mul (dec (mul (g[j - 1],p + 1),f[j - 1]),getinv (j));
memcpy (f,g,sizeof (g));
}
write (mul (ans,n)),putchar ('\n');
return 0;
}