【题解】 【集训队作业2018】喂鸽子 minmax容斥+期望dp+补集转化 UOJ449
Legend
有 \(n\) 只鸽子,每秒钟你随机选一只鸽子喂给它 \(1\) 粒玉米,当一只鸽子吃到 \(k\) 粒玉米时会饱,求所有鸽子饱的期望时间。
\(0 \le n \le 50\),\(0 \le k \le 1000\)。模数 \(998244353\)。
Editorial
不难发现可以 \(\textrm{min-max}\) 容斥。
设 \(F_i\) 为选中 \(i\) 只鸽子的子集,并给所有 \(n\) 只鸽子喂玉米,期望喂多少次玉米后,在该子集中第一次出现被喂饱的鸽子。
根据 \(\textrm{min-max}\) 容斥,注意到我们不关心子集的具体构成,只关心大小,所以答案即为:
现在问题只有,怎么求 \(F_i\)。求期望不好求,我们先求方案数,设 \(G_{i,j}\) 表示喂 \(i\) 只鸽子玉米,喂了 \(j\) 次还没有任何鸽子被喂饱的方案数。
\(G\) 数组直接背包复杂度高达 \(O(n^3k)\),过不了,不过可以很容易用 \(\textrm{EGF}\)(指数型生成函数)优化到 \(O(n^2k\log nk)\),但这依赖于本题的特殊模数(\(998244353\))。下介绍一种不用 \(\textrm{NTT}\) 的方法——补集转化。
我们考虑用所有方案减去不合法的方案,可以得到如下转移方程:
其含义可以这样解释:
- 每次我们考虑新增一粒玉米,喂给 \(i\) 只鸽子中的任意一只。
- 这样子会出现不合法情况,即某一只鸽子之前没饱,被喂了之后就饱了。枚举一下这只被喂饱的鸽子是谁,和它是在哪几次被喂过玉米,算出方案减去。
然后我们就可以通过 \(O(n^2k)\) 的时间算出 \(G\)(方案数)。
那么怎么求期望时间 \(F\) 呢?
其含义就是枚举除了饱了的鸽子,其它的鸽子一共吃了多少,然后按期望的定义算就行了。前面乘一个 \(\frac{n}{i}\) 是因为在 \(n\) 只鸽子内随机时,你只有 \(\frac{i}{n}\) 的概率选到这个子集的鸽子。
然后就可以毫无压力地做这个题了。
Code
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
freopen(#x".in" ,"r" ,stdin);\
freopen(#x".out" ,"w" ,stdout)
#define LL long long
const int MX = 50 + 2;
const LL MOD = 998244353;
const LL phi = MOD - 1;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
LL qpow(LL a ,LL b ,LL p = MOD){
LL ans = 1;
while(b){if(b & 1) ans = ans * a % p;
a = a * a % p ,b >>= 1;
}return ans;
}
int n ,k;
LL dp[MX][50 * 999 + 2] ,S[MX][50 * 999 + 2] ,F[MX];
// dp[i][j] 表示给 i 只不同的鸽子喂 j 粒玉米且都没饱的方案数
// F[i] 表示给 i 只鸽子喂饱第一只的期望时间
LL C(int n ,int m);
LL fac[50000 + 23] ,inv[50000 + 23];
void init(){
fac[0] = 1 ,inv[0] = inv[1] = 1;
for(int i = 1 ; i <= 50000 ; ++i) fac[i] = fac[i - 1] * i % MOD;
for(int i = 2 ; i <= 50000 ; ++i) inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
for(int i = 2 ; i <= 50000 ; ++i) inv[i] = inv[i] * inv[i - 1] % MOD;
dp[0][0] = 1;
for(int i = 1 ; i <= n ; ++i){
dp[i][0] = 1;
for(int j = 1 ; j <= (k - 1) * i + 1 ; ++j){
dp[i][j] = i * (dp[i][j - 1] - C(j - 1 ,k - 1) % MOD * (j - k >= 0 ? dp[i - 1][j - k] : 0) % MOD + MOD) % MOD;
}
}
for(int i = 1 ; i <= n ; ++i){ // 枚举被喂了的总鸽子数量
F[i] = 0;
for(int j = 0 ; j <= (k - 1) * (i - 1) ; ++j){ // 枚举其它鸽子吃了多少
F[i] = (F[i] + C(j + k - 1 ,k - 1) * (j + k) % MOD * dp[i - 1][j] % MOD * qpow(i ,(j + k) * (MOD - 2) % phi)) % MOD;
}
// F[i] = F[i] * i % MOD;
}
}
int main(){
n = read() ,k = read();
init();
LL ans = 0;
for(int i = 1 ; i <= n ; ++i){
ans += (i & 1 ? 1 : -1) * F[i] * C(n ,i) % MOD * n % MOD/* * qpow(i ,MOD - 2) % MOD*/;
}
ans = (ans % MOD + MOD) % MOD;
printf("%lld\n" ,ans);
return 0;
}
LL C(int n ,int m){return m > n || m < 0 ? 0 : fac[n] * inv[m] % MOD * inv[n - m] % MOD;}