P9584 城市 题解。
1. 题意
给出一棵 个点的树,边有边权,每次询问添加一个点 和一条边 后任意两点之间的距离和。
2. 题解
考虑一条边对答案的贡献,即为通过这条边的路径数 边权。
为了方便计算,我们将边的贡献转为点的贡献:设 表示 到父亲的边权, 表示子树大小, 表示子树外大小,则点 的贡献为 。
然后考虑加了一条边 之后怎么算,不妨设 是 的父亲。于是 到根路径上每个点的子树大小都要 ,其余点的子树外大小都要 ,另外答案还要加上新边的贡献。
这样对于每个询问暴力加边计算是 的,但其实可以直接预处理。发现新边的贡献比较好算,即 ,于是考虑预处理每个点除去新边的贡献的答案。
我们先 DFS 预处理出每个点的 、、,另外因为加了一个点所以预处理子树外大小时 。
然后我们再 DFS 一遍求出每个点的答案,当遍历到一个点 时 ,,同时更新当前答案,回溯时再改回来即可。
最后处理询问的时候,将预处理的答案加上新边贡献即可,然后还要 因为每条路径要算两次。
时间复杂度 。
3. Code
#include <iostream> #include <vector> using namespace std; using i64 = long long; const int N = 2e5 + 5, P = 998244353; int n, m, a[N], f[N], g[N], ans[N], cur; vector<pair<int, int>> G[N]; void dfs1(int u, int fa) { f[u] = 1; for (auto [v, w] : G[u]) if (v != fa) a[v] = w, dfs1(v, u), f[u] += f[v]; g[u] = n + 1 - f[u]; cur = (cur + (i64)a[u] * f[u] * g[u]) % P; } void dfs2(int u, int fa) { cur = (cur - (i64)a[u] * f[u] * g[u] + P) % P; ++f[u], --g[u]; cur = (cur + (i64)a[u] * f[u] * g[u]) % P; ans[u] = cur; for (auto [v, w] : G[u]) if (v != fa) dfs2(v, u); cur = (cur - (i64)a[u] * f[u] * g[u] + P) % P; --f[u], ++g[u]; cur = (cur + (i64)a[u] * f[u] * g[u]) % P; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> m; for (int i = 1, u, v, w; i <= n - 1; i++) { cin >> u >> v >> w; G[u].emplace_back(v, w); G[v].emplace_back(u, w); } dfs1(1, 0); dfs2(1, 0); for (int i = 1, u, w; i <= m; i++) { cin >> u >> w; cout << (ans[u] + (i64)n * w) * 2 % P << '\n'; } return 0; }
cpp