杀蚂蚁简单版
杀蚂蚁简单版
杀蚂蚁 (antbusters) 是一T3款风靡 Ol 界的游戏,下面介绍它的一个简单版本。游戏的地图是一棵 \(n\) 个节点的树, 其中 1 号点时蚂蚁窝, 并且 1 号点只与 2 号点直接相连。
现在有一只蚂蚁拿着蛋糕从树上某个节点 \(s\) 出发, 按照如下规则随机游走:
1.树上每一个节点\(i\) 都有一定数量的信息素 \(v_i\)。
2.如果蚂蚁当前位于节点\(x\),与\(x\) 相邻的节点分别为\(y_1,y_2,\cdots,y_r\)。那么蚂蚁下一步走到节点\(y_i\quad(1\leq i\leq r)\) 的概率是
3.如果蚂蚁在某一步移动结束后位于 1 号点 (蚂蚁窝),游戏结束。
现在你有一个攻击装置,这个攻击装置的工作原理是:
1.游戏开始时,你需要选择树上的一条简单路径,这个路径就是该装置的攻击范围,在之后的游戏进程中你不可以改变它。
2.之后在每秒初,如果蚂蚁在这条路径上, 则蚂蚁就会被攻击而受到 \(1\) 点伤害。
你现在要进行 \(q\) 次游戏,每次游戏会给定蚂蚁出生点 \(s\) ,你想知道。如果在这次游戏中选择 \(x\) 号点到 \(y\) 号点这条简单路径作为攻击范围, 在游戏结束时,你对蚂蚁造成伤害的期望值是多少,由于答案非常大而且可能是个分数,你只需要输出答案对 998 244 353 取模的结果。
这里假设蚂蚁的血量是无限的,这意味着蚂蚁走到1 号点时游戏才会结束。
%%% EXODUS
先考虑进行一个较为难想的 DP,一般来说,这种游走类问题是将父亲作为一个阶段来考虑,因为父亲是唯一确定的
考虑设 \(f_u\) 为 \(u\) 第一次到达其父亲期望经过的关键点的个数,设其附近的点的总权值和为 \(tot_u\),如果 \(u\) 为关键点,则 \(mrk_u=1\),则经过一番推导可以得到
其中求和部分表示从当前的节点到了一个儿子后再回来的期望,而后面则是从自己回到父亲的期望
一番导的推之后,会有以下结果
这里就有一个可以计算的转移了,有了 \(O(nq)\) 的做法
不难发现一件事情,对于不同的 \(s,x,y\) ,只有\(mrk\) 是不同的,这启发我们对着路径上的关键点算出所有的值
这里再看一下这个转移柿子,这是一个只与子树有关的转移,考虑将自己的贡献以及子树对自己的贡献拆出来
考虑一个点 \(u\) 对于其祖先 \(v\) 的贡献,不难发现是 \(\dfrac{tot_u}{val_{fa_u}}\times \dfrac{val_u}{val_{fa_{fa_u}}}\times\dots\),可以发现化简之后是\(\dfrac{tot_uval_u}{val_{fa_v}val_v}\)
考虑分开贡献这个东西,设 \(h1=lca(x,s),h2=lca(y,s),h=lca(x,y)\)
分两种情况讨论
- \(h1=h2\)
不难发现这个时候从 \(x,y\) 到 \(h1\) 的路径是等价的,求出这两条链上的 \(tot_uval_u\) 再乘上 \(h1\) 到根路径上的 \(\dfrac{1}{val_{fa_v}val_v}\) 就可以了
- \(h1\ne h2\)
考虑设 \(h2=h\) ,那将链拆为三个部分,从 \(x\) 到 \(h1\) 的部分可以给 \(h1\) 到根的部分做贡献,从 \(y\) 到 \(h\) 的部分可以给 \(h\) 到根的部分做贡献,而从 \(h1\) 到 \(h\) 的部分每一部分都可以给自己的祖先做贡献,分别维护前缀和,然后树上差分就可以了
复杂度瓶颈在 lca 和求逆元
const int N = 1e5 + 10;
int n, q;
int acc[20][N], dep[N], fa[N];
ll val[N], c[3][N], tot[N];
vector<int> g[N];
ll inv(ll x, ll p = mod - 2) {
ll ans = 1;
for (; p; p >>= 1) {
if (p & 1)
(ans *= x) %= mod;
(x *= x) %= mod;
}
return ans;
}
void dfs(int now, int f) {
acc[0][now] = f, dep[now] = dep[f] + 1;
for (int u = acc[0][now], i = 0; u; u = acc[++i][now]) acc[i + 1][now] = acc[i][u];
ll h1 = tot[now] * val[now] % mod, h2 = inv(val[now] * val[f] % mod);
c[0][now] = (c[0][f] + h1) % mod;
c[1][now] = (c[1][f] + h2) % mod;
c[2][now] = (c[2][f] + h1 * c[1][now] % mod) % mod;
for (int x : g[now])
if (x ^ f)
dfs(x, now);
}
int lca(int x, int y) {
if (dep[x] < dep[y])
swap(x, y);
for (int gap = dep[x] - dep[y], bit = __lg(gap); gap; gap -= (1 << bit), bit = __lg(gap)) x = acc[bit][x];
fd(i, __lg(dep[x]), 0) if (acc[i][x] ^ acc[i][y]) x = acc[i][x], y = acc[i][y];
return (x ^ y) ? acc[0][x] : x;
}
ll calc(int x, int y, int d) {
int z = lca(x, y);
ll res = (c[d][x] + c[d][y]) % mod;
res = (res + mod - c[d][z]) % mod;
res = (res + mod - c[d][acc[0][z]]) % mod;
return res;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
n = rd();
fp(i, 1, n) val[i] = rd();
fp(i, 1, n - 1) {
int u = rd(), v = rd();
(tot[u] += val[v]) %= mod;
(tot[v] += val[u]) %= mod;
g[u].emplace_back(v), g[v].emplace_back(u);
}
dfs(2, 1);
q = rd();
while (q--) {
int s = rd(), x = rd(), y = rd();
int h = lca(x, y), h1 = lca(x, s), h2 = lca(y, s);
ll ans = 0;
if (h1 == h2) {
ans = calc(x, y, 0) * calc(h1, 2, 1) % mod;
} else {
if (h == h1)
swap(x, y);
h1 = lca(x, s);
ans = (calc(x, h1, 0) + mod) % mod * calc(h1, 2, 1) % mod;
(ans += (calc(y, h, 0) + mod - calc(h, h, 0)) % mod * calc(h, 2, 1) % mod) %= mod;
(ans += calc(acc[0][h1], h, 2)) %= mod;
}
cout << ans << endl;
}
return 0;
}
浙公网安备 33010602011771号