51NOD 1424 零树

Discription

有一棵以1为根的树,他有n个结点,用1到n编号。第i号点有一个值vi。

现在可以对树进行如下操作:

步骤1:在树中选一个连通块,这个连通块必须包含1这个结点。

步骤2:然后对这个连通块中所有结点的值加1或者减1。

问最少要经过几次操作才能把树中所有结点都变成0。

注意:步骤1与步骤2合在一起为一次操作。


Input

单组测试数据。 
第一行有一个整数n(1 ≤ n ≤ 10^5) 
接下来n-1行,每行给出 ai 和 bi (1 ≤ ai, bi ≤ n; ai ≠ bi),表示ai和bi之间有一条边,输入保证是一棵树。 
最后一行有n个以空格分开的整数,表示n个结点的值v1, v2, ..., vn (|vi| ≤ 10^9)。

Output

输出一个整数表示最少的操作步数。

Sample Input

3
1 2
1 3
1 -1 1

Sample Output

3


题解见注释(真的不知道我怎么想到差分约束的2333,网上全是用树上dp做的。。。)
/*
    考虑差分约束。
	1.设每个点i被add(i)次加操作涉及到,被dec(i)次减操作涉及到, 那么:
	    add(i) - dec(i) + w[i] = 0.
	2.把dec用add表示后,每个点有两个限制:
	    (1). add(i) >= max{0,-w[i]}
	    (2). add(i) <= add(fa) + min{0,w[fa]-w[i]} 
	
	然后直接跑一个最短路,2*add(1) + w[1] 就是答案. 
*/
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=100005;
vector<int> g[maxn];
int ne[maxn*5],to[maxn*5],num;
int hd[maxn],val[maxn*5],n,m,w[maxn];
bool iq[maxn];
ll d[maxn];

inline void add(int x,int y,int z){
	to[++num]=y,ne[num]=hd[x],hd[x]=num,val[num]=z;
}

void dfs(int x,int fa){
	add(fa,x,min(0,w[fa]-w[x]));
	for(int i=g[x].size()-1,TO;i>=0;i--){
		TO=g[x][i];
		if(TO==fa) continue;
		dfs(TO,x);
	}
}

inline void build(){
	for(int i=1;i<=n;i++) add(i,0,min(0,w[i]));
	dfs(1,1);
}

inline void spfa(){
	queue<int> q;
	for(int i=0;i<=n;i++) q.push(i),iq[i]=1;
	int x;
	while(!q.empty()){
		x=q.front(),q.pop();
		for(int i=hd[x];i;i=ne[i]) if(d[x]+(ll)val[i]<d[to[i]]){
			d[to[i]]=d[x]+(ll)val[i];
			if(!iq[to[i]]) q.push(to[i]),iq[to[i]]=1;
		}
		iq[x]=0;
	}
	for(int i=1;i<=n;i++) d[i]-=d[0];
}

int main(){
	scanf("%d",&n);
	int uu,vv;
	for(int i=1;i<n;i++){
		scanf("%d%d",&uu,&vv);
		g[uu].pb(vv),g[vv].pb(uu);
	}
	for(int i=1;i<=n;i++) scanf("%d",w+i);
	
	build();
	spfa();
	
	printf("%lld
",d[1]*2+(ll)w[1]);

	return 0;
}

  

 
原文地址:https://www.cnblogs.com/JYYHH/p/8807625.html