[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;
}
posted @ 2022-02-11 16:13  落花月朦胧  阅读(49)  评论(1编辑  收藏  举报