[ZJOI2007] 时态同步 题解
[ZJOI2007] 时态同步 题解
题目大意
给出一颗带权值的点数为 \(n\) 的树,问最少操作多少次可以让这棵树的根节点 \(s\) 到这棵树的所有叶子结点距离相等。
题解
显然,树 \(+ \texttt{DP} =\) 树上\(\texttt{DP}\)。
状态设 \(f_i\) 为以 \(i\) 为根节点的子树中使 \(i\) 到这棵树的叶子节点距离相等的最小操作数量。
答案就是 \(f_s\)。
显然本题的操作只有加没有减, 所以我们只能让所有的距离都加成最远的距离。
所以我们要先处理任何结点到叶子节点的最远距离。 记作 \(dis\), 所以 \(dis_x\)即 \(x\) 到叶节点的最远距离。
可以用 \(dfs\) 求得,转移方程就是 \(dis_u=\max{(dis_v+val_{u,v})}\)。 \(val_{u,v}\) 表示 \(u\) 到 \(v\) 的权值。
考虑 \(f\) 数组的状态转移。
假设现在转移到了 \(f_u\),用 \(v\) 表示 \(u\) 的子节点,可以想到对于以 \(v\) 为根的子树,到叶节点的最远距离为 \(dis_v\),如果选择走这条路, 对于 \(u\) 来说, 距离就是 \(dis_v + val_{u, v}\),而以 \(u\) 为根的子树到叶节点的最远距离为 \(dis_u\),那么 \(dis_u-(dis_v + val_{u, v})\) 就是需要增加的数值,即 \(val_{u, v}\) 需要增加的量。
那么转移方程就是 \(f_u=\sum_{v\in u}(dis_u-(dis_v+val_{u, v}))\), 利用 \(dfs\) 实现。
注意开 \(\texttt{long long}\)
#include <bits/stdc++.h>
using i64 = long long;
constexpr i64 N = 500010;
struct node {
i64 dis, val, next;
} edge[N << 1];
i64 siz, head[N + 1];
inline void add(i64 from, i64 dis, i64 val) {
edge[++siz].dis = dis;
edge[siz].val = val;
edge[siz].next = head[from];
head[from] = siz;
}
i64 n, s;
i64 dis[N + 1], f[N + 1];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
std::cin >> n >> s;
for (i64 i = 1; i < n; i++) {
i64 u, v, w;
std::cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
// 处理最远距离
std::function<void(i64, i64)> dfs1 = [&](i64 u, i64 fa) {
for (i64 i = head[u]; i; i = edge[i].next) {
i64 v = edge[i].dis;
if (v != fa) {
dfs1(v, u);
dis[u] = std::max(dis[u], dis[v] + edge[i].val);
}
}
};
// 处理最小操作次数
std::function<void(i64, i64)> dfs2 = [&](i64 u, i64 fa) {
for (i64 i = head[u]; i; i = edge[i].next) {
i64 v = edge[i].dis;
if (v != fa) {
dfs2(v, u);
f[u] = f[u] + f[v] + dis[u] - (dis[v] + edge[i].val);
}
}
};
dfs1(s, 0);
dfs2(s, 0);
std::cout << f[s] << std::endl;
return 0;
}