[做题记录] ZJOI2019 开关 题解
ZJOI2019 开关
题意
有 \(n\) 个开关, 初始的时候都是 \(0\), 给出一个状态 \(s\) , 每次以 \(\frac{p_i}{\sum_{j = 1}^np_j}\) 的概率按下一个开关 \(i\) 期望多少次以后可以第一次达到这个状态。
\(n \leq 100, \sum p_i \leq 5\times 10^4\)
题解
令 \(a_i = \frac{p_i}{\sum_{i = 1}^np_j}, m = \sum_{i = 1}^n p_i\) 。
考虑生成一个操作序列, 讨论每个位置要按下的奇偶性, 可以直接写出操作序列出现概率的EGF。
但是这个东西显然啥用也没有, 因为我们要求出的是第一次到达这个状态的EGF, 这个求出来的显然只是所有最终状态是 \(s\) 的EGF。
考虑一个经典的构造, 令第一次到达状态 \(s\) 的生成函数是 \(H(x)\), 若干次操作以后回到原状态的序列的生成函数是 \(\hat{G}(x)\) , 那么答案就是 \(H'(1)\) 。
不难写出 \(\hat{G}(x)\) 的表达式, 也就是 \(s_i\) 全部是 偶数的情况。
考虑一个组合意义, “到达 \(s\) 的概率 \(=\) 第一次到达 \(s\) 的概率, 经过若干次重新到达 \(s\) 的概率(可以是\(0\)次)”。
这显然只是一个卷积的意义, 可以得到方程 \(F = GH\) 。也就是 \(H = \frac{F}{G}\) 。
注意这里的 \(F, G\) 是上文 \(EGF\) 的 \(OGF\) 形式。
简单求导可以得到 \(H'(1) = \frac{F'(1)G(1) - F(1)G'(1)}{G^2(1)}\)。现在问题变成对 \(F', G', F, G\) 求系数和。
以 \(F\) 为例考虑对其动手动脚。
令
那么
这一步操作仅仅是转了一次OGF。
然后现在考虑求一波系数, 但是发现有点问题在于 \(F\) 当 \(j = m, x = 1\) 的时候没意义了。
然后你点开了题解发现大家都乘了一个 \(1 - x\)。
考虑把 \(F\) 和 \(G\) 同时乘一个 \(1 - x\) 答案是不变的, 而且发现此时函数 \(F, G\) 均收敛。
然后再考虑计算其系数。
设 \(P(x) = F(x)(1 - x), Q(x) = G(x)(1 - x)\) 。
通过求导容易知道 \(P(1) = f_m, P'(1) = \sum_{-m \leq j \leq m - 1}f_j\frac{m}{j - m}\) 。
\(Q, Q'\) 可以同理得出。
直接大力背包出 \(f_i, g_i\) 就可以暴算了。
#include <bits/stdc++.h>
const int P = 998244353;
inline int mod(int x) { return x + (x >> 31 & P); }
inline void pls(int &x, int y) { x = mod(x + y - P); }
inline void sub(int &x, int y) { x = mod(x - y); }
inline int power(int x, int k) {
int res = 1; if(x < 0) x += P;
while(k) {
if(k & 1) res = 1ll * res * x % P;
x = 1ll * x * x % P; k >>= 1;
}
return res;
}
const int N = 100 + 5, M = 5e4 + 5;
int n, s[N], p[N], m;
int pool[3][M << 1];
int *F = pool[0] + M, *G = pool[1] + M, *t = pool[2] + M;
int main() {
std :: ios :: sync_with_stdio(false);
std :: cin.tie(0); std :: cout.tie(0);
std :: cin >> n;
for(int i = 1; i <= n; i ++) std :: cin >> s[i];
for(int i = 1; i <= n; i ++) std :: cin >> p[i], m += p[i];
F[0] = 1;
for(int i = 1; i <= n; i ++) {
int sgn = (s[i] == 1 ? P - 1 : 1);
for(int j = - m; j <= m; j ++)
if(F[j])
pls(t[j + p[i]], F[j]), pls(t[j - p[i]], 1ll * F[j] * sgn % P);
for(int j = - m; j <= m; j ++) F[j] = t[j], t[j] = 0;
}
G[0] = 1;
for(int i = 1; i <= n; i ++) {
for(int j = - m; j <= m; j ++)
if(G[j]) pls(t[j + p[i]], G[j]), pls(t[j - p[i]], G[j]);
for(int j = - m; j <= m; j ++) G[j] = t[j], t[j] = 0;
}
int p1 = F[m], p2 = 0, q1 = G[m], q2 = 0;
for(int i = - m; i <= m - 1; i ++)
pls(p2, 1ll * F[i] * m % P * power(i - m, P - 2) % P);
for(int i = - m; i <= m - 1; i ++)
pls(q2, 1ll * G[i] * m % P * power(i - m, P - 2) % P);
int ans = 1ll * (1ll * p2 * q1 % P - 1ll * p1 * q2 % P + P) * power(1ll * q1 * q1 % P, P - 2) % P;
std :: cout << ans << std :: endl;
return 0;
}