AtCoder Grand Contest 038 E: Gachapon
题目传送门:AGC038E。
题意简述
有 \(N\) 个物品,给定 \(A_0 \sim A_{N - 1}\) 和 \(B_0 \sim B_{N - 1}\)。
每一个单位时间你会获得一个物品,第 \(i\) 个物品有 \(\dfrac{A_i}{\sum_{j = 0}^{N - 1} A_j}\) 的概率获得。
计算第一次对于每个 \(i\) 都有第 \(i\) 个物品获得至少 \(B_i\) 个的期望时间。
- \(\displaystyle \sum_{i = 1}^{N - 1} A_i \le 400\)
- \(\displaystyle \sum_{i = 1}^{N - 1} B_i \le 400\)。
题解
令 \(\displaystyle \Sigma A = \sum_{i = 0}^{N - 1} A_i\) 以及 \(\displaystyle \Sigma B = \sum_{i = 0}^{N - 1} (B_i - 1)\)。
使用 Min-Max 容斥,转化为对于每个 \(\{0, 1, \ldots , N - 1\}\) 的非空子集计算第一次子集中的任意一个物品获得至少 \(B_i\) 个的期望时间,如果集合大小为奇数,则对答案贡献 \(1\) 倍,否则贡献 \(-1\) 倍。
也就是满足子集中的所有物品获得的数量都少于 \(B_i\) 个的时刻数量,加上 \(1\)。
如果我们把开始的没有任何物品的状态也算作一个时刻,那么 \(+1\) 就省了。
现在考虑对于一个非空子集 \(S \subseteq \{0, 1, \ldots , N - 1\}\) 计算子集中的所有物品获得的数量都少于 \(B_i\) 个的时刻数量(包括初始时刻)。
这也就是对于每一种物品获得的状态,计算这种状态在所有可能情况下出现的期望次数(包括初始时刻),再求和。
我们枚举每一种状态,用 \(x\) 表示,其中 \(x_i\) 表示 \(i\) 获得的个数(\(i \in S\)),必须保证 \(0 \le x_i < B_i\)。
接下来需要计算这种状态在所有可能情况下出现的期望次数。
注意到一旦第一次变成了这种状态,只要再次获得 \(S\) 中的物品,状态就会改变。
令 \(P\) 为一次获得 \(S\) 中的物品的概率,也就是 \(\dfrac{\sum_{i \in S} A_i}{\Sigma A}\)。可以发现一旦第一次变成这种状态之后,状态会期望持续 \(1 / P\) 个时刻(包括第一次的时刻)。对于固定的 \(S\),\(P\) 都是不变的,所以只要求出每一种状态出现的概率再最后乘以 \(1 / P\) 就行了。
考虑计算每一种状态出现的概率,这时就不需要考虑 \(S\) 以外的物品的影响了,它们不会影响出现概率。
那么一种状态的概率就是存在一个获得 \(S\) 中物品的前缀满足 \(i\) 恰好出现了 \(x_i\) 次。
令 \(\displaystyle X = \sum_{i \in S} x_i\) 以及 \(\displaystyle C = \sum_{i \in S} A_i\),则概率就是 \(\displaystyle X! \prod_{i \in S} {\left( \frac{A_i}{C} \right)}^{x_i} \frac{1}{(x_i)!}\),这些阶乘来自于多重组合数。
利用乘法分配律,以 \(X\) 为 DP 的状态进行转移,我们就可以对于所有可能的状态求出这个概率的和,转移其实类似于指数型生成函数的卷积。
而如果需要对所有的 \(S\) 求和,也是类似的,需要注意处理一下容斥系数。然而对于不同的 \(S\),\(C\) 是不固定的,不过我们可以直接把 \(C\) 提出来,也就是 \(\displaystyle \frac{X!}{C^X} \prod_{i \in S} \frac{A_i^{x_i}}{(x_i)!}\),然后 DP 的时候记录 \(X, C\) 两维状态即可。
这样依次加入 \(N\) 个物品,每次加入的时间复杂度为 \(\mathcal O (B_i X C)\) 进行暴力转移。
下面是代码,时间复杂度为 \(\mathcal O({(\Sigma B)}^2 \Sigma A)\):
#include <cstdio>
typedef long long LL;
const int Mod = 998244353;
const int MN = 405;
inline int qPow(int b, int e) {
int a = 1;
for (; e; e >>= 1, b = (LL)b * b % Mod)
if (e & 1) a = (LL)a * b % Mod;
return a;
}
int Fac[MN], iFac[MN];
void Init(int N) {
Fac[0] = 1;
for (int i = 1; i <= N; ++i) Fac[i] = (LL)Fac[i - 1] * i % Mod;
iFac[N] = qPow(Fac[N], Mod - 2);
for (int i = N; i >= 1; --i) iFac[i - 1] = (LL)iFac[i] * i % Mod;
}
int N, A[MN], B[MN], SA, SB;
int f[MN][MN], g[MN][MN], Ans;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
scanf("%d%d", &A[i], &B[i]),
SA += A[i], SB += B[i] - 1;
Init(SB);
SA = SB = 0;
f[0][0] = Mod - 1;
for (int i = 1; i <= N; ++i) {
for (int j = 0; j <= SA + A[i]; ++j)
for (int k = 0; k <= SB + B[i] - 1; ++k)
g[j][k] = 0;
for (int j = 0; j <= SA; ++j) {
int *G = g[j + A[i]];
for (int k = 0; k <= SB; ++k)
for (int x = 0, c = 1; x < B[i]; ++x, c = (LL)c * A[i] % Mod)
G[k + x] = (G[k + x] + (LL)c * iFac[x] % Mod * f[j][k]) % Mod;
}
SA += A[i], SB += B[i] - 1;
for (int j = 0; j <= SA; ++j)
for (int k = 0; k <= SB; ++k) {
f[j][k] -= g[j][k];
f[j][k] += (f[j][k] >> 31) & Mod;
}
}
for (int j = 1; j <= SA; ++j) {
int v = qPow(j, Mod - 2);
int c = (LL)SA * v % Mod;
for (int k = 0; k <= SB; ++k, c = (LL)c * v % Mod)
Ans = (Ans + (LL)Fac[k] * c % Mod * f[j][k]) % Mod;
}
printf("%d\n", Ans);
return 0;
}