[ZJOI2007] 时态同步(树形dp)

传送门

很好的一道树形dp的题目
  • 根据所给题意可以总结为:从激励点出发求到所有叶子节点时间相同的最小次数

  • 由于要找最小次数,考虑贪心的方法,要使次数尽量小,也就是要使子节点的公共边尽可能的大。

  • 有一点可以肯定的是一定要知道时间最长是多少,然后其他树枝才能根据这个时间求最小值,而这个最长时间是很容易求得的。

  • 求完最长时间后,考虑其他树枝,如何判断公共树枝要加多少呢?方法就是知道从它的最下面的叶子节点到它这里最远要多长时间。知道最远时间后,就能求得一个时间差,要让这个节点内的所有点的权值都相同,这个差也就是将这个节点最大的一个子节点依次减去其他节点的权值,然后将这些差值累加起来。最后的答案就是这个统计的差值。

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;
typedef long long ll;
bool vis[N];
ll n, tot, s, head[N], dp[N], maxn[N], ans;

struct Edge{
	ll v, w, next;
}edge[N * 2];

void add(ll u, ll v, ll w){
	edge[tot].v = v;
	edge[tot].w = w;
	edge[tot].next = head[u];
	head[u] = tot ++;
}

void dfs(int now, int fa){
	for(int i = head[now]; i != -1; i = edge[i].next){
		int v = edge[i].v;
		int w = edge[i].w;
		if(v == fa)
			continue;
		dfs(v, now);
		dp[now] = max(dp[now], dp[v] + w);		//找出当前节点的时间最长的一个节点
	}
	for(int i = head[now]; i != -1; i = edge[i].next){
		int v = edge[i].v;
		int w = edge[i].w;
		if(v == fa)
			continue;
		ans += (dp[now] - (dp[v] + w));			//统计差值
	}
}
int main(){
	scanf("%lld%lld", &n, &s);	
	memset(head, -1, sizeof head);
	for(int i = 1; i < n; i ++){
		ll a, b, w;
		scanf("%lld%lld%lld", &a, &b, &w);
		add(a, b, w);
		add(b, a, w);
	}

	dfs(s, 0);

	printf("%lld
", ans);
	return 0;
} 
原文地址:https://www.cnblogs.com/pureayu/p/14984382.html