1. 程式人生 > 其它 >[ZJOI2007] 時態同步(樹形dp)

[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\n", ans);
	return 0;
}