【题解】 【集训队作业2018】喂鸽子 minmax容斥+期望dp+补集转化 UOJ449

Legend

(n) 只鸽子,每秒钟你随机选一只鸽子喂给它 (1) 粒玉米,当一只鸽子吃到 (k) 粒玉米时会饱,求所有鸽子饱的期望时间。

(0 le n le 50)(0 le k le 1000)。模数 (998244353)

Editorial

不难发现可以 ( extrm{min-max}) 容斥。

(F_i) 为选中 (i) 只鸽子的子集,并给所有 (n) 只鸽子喂玉米,期望喂多少次玉米后,在该子集中第一次出现被喂饱的鸽子。

根据 ( extrm{min-max}) 容斥,注意到我们不关心子集的具体构成,只关心大小,所以答案即为:

[sum_{i=1}^{n}inom{n}{i}(-1)^{i+1}F_i ]

现在问题只有,怎么求 (F_i)。求期望不好求,我们先求方案数,设 (G_{i,j}) 表示喂 (i) 只鸽子玉米,喂了 (j) 次还没有任何鸽子被喂饱的方案数。

(G) 数组直接背包复杂度高达 (O(n^3k)),过不了,不过可以很容易用 ( extrm{EGF})(指数型生成函数)优化到 (O(n^2klog nk)),但这依赖于本题的特殊模数((998244353))。下介绍一种不用 ( extrm{NTT}) 的方法——补集转化。

我们考虑用所有方案减去不合法的方案,可以得到如下转移方程:

[G_{i,j}=i sum_{k=0}^{j-1} G_{i,j-1}-inom{j-1}{k-1}G_{i-1,j-k} ]

其含义可以这样解释:

  1. 每次我们考虑新增一粒玉米,喂给 (i) 只鸽子中的任意一只。
  2. 这样子会出现不合法情况,即某一只鸽子之前没饱,被喂了之后就饱了。枚举一下这只被喂饱的鸽子是谁,和它是在哪几次被喂过玉米,算出方案减去。

然后我们就可以通过 (O(n^2k)) 的时间算出 (G)(方案数)。

那么怎么求期望时间 (F) 呢?

[F_i = frac{n}{i} imes isum_{j=0}^{(i-1)(k-1)}(j+k) frac{inom{j+k-1}{k-1}G_{i-1,j}}{i^{j+k}} ]

其含义就是枚举除了饱了的鸽子,其它的鸽子一共吃了多少,然后按期望的定义算就行了。前面乘一个 (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
" ,ans);
	return 0;
}

LL C(int n ,int m){return m > n || m < 0 ? 0 : fac[n] * inv[m] % MOD * inv[n - m] % MOD;}
原文地址:https://www.cnblogs.com/imakf/p/14373681.html