题目描述
一棵树是一个有个节点与正好
条边的图;并且符合以下条件:对于任意两个节点之间有且只有一条简单路径。
我们定义树T的子树为一棵所有节点是树T节点的子集,所有边是T边的子集的树。
给定一颗有个节点的树,假设它的节点被编号为
到
。每个节点有一个权值,
表示编号为
的节点的权值。你需要进行一些操作,每次操作符合以下规定:
- 在给定的这棵树中选择一棵子树,并保证子树中含有节点1
- 把这棵子树中的所有节点加上或减去1
你需要计算至少需要多少次操作来让所有的节点的权值归零。 输入数据
第一行包含一个整数,表示树中节点的数量
接下来的行,一行两个整数u,v,表示u和v之间有一条边
。
最后一行包含个整数
,用空格隔开,表示每个节点的权值 输出数据
一行一个整数,输出最小需要的操作次数。
输入样例
3
1 2
1 3
1 -1 1
输出样例
3
数据规模
对于30%的数据有
对于50%的数据,
对于100%的数据,
分析
1、题目解释
给定一棵树,每次可以选择一个子树(含有节点1),该子树上的每个点的权值 或
。问最少多少次该树变为全
。
2、样例解释
首先建图
不难看出答案
多写一个样例吧这个样例太垃圾;
7
1 2
1 3
2 4
4 5
3 6
3 7
6 2 4 -4 1 5 -3
画出图为这样
按照将加法的先减,减法的再加可以得出(自己暴力数)得:
ans=16
3、思路
看到求最小值且因为只改变一个点的值并没有改变后面的值,考虑dp。因为在树上所以为树型dp。
由样例不难想出,对于每一颗子树而言,其根节点最少被加上其子节点中权值最小的值的绝对值,最少被减去其子节点中的最大值。
e.g.对于两个叶子节点a=7,b=5,则b--一直减到最后a=2,b=0,这时侯就减去a就可以了,次数就是7
定义:为需要加的次数,
为减法的个数,其中
表示当前根。
那么可以得出:
初始化:如果,则
(为负数需要加),否则
验证(关于上面的样例)
可以自己下去验证一下:(记得将子节点加减的指加到父节点上)
add[1]=5 sub[1]=11
add[2]=5 sub[2]=7
add[3]=3 sub[3]=7
add[4]=5 sub[4]=1
add[5]=0 sub[5]=1
add[6]=0 sub[6]=5
add[7]=3 sub[7]=0
代码
#include <bits/stdc++.h>
using namespace std;
typedef int intt;
#define int long long
const int maxn = 1e5 + 10;
#define F(a,b,c) for(int a=b;a<=c;a++)
#define Fo(a,b,c) for(int a=b;a>=c;a--)
int n;
int a[maxn],add[maxn], sub[maxn];
struct Edge {
int v,nxt;
} e[maxn*2];
int head[maxn], idx;
void edge(int u,int v) {
e[++idx].v = v;
e[idx].nxt = head[u];
head[u] = idx;
}
void debug(int i) {
cout<<endl;
cout<<"add["<<i<<"]="<<add[i]<<" ";
cout<<"sub["<<i<<"]="<<sub[i]<<endl;
}
void dfs(int u,int fa) {
for(int i=head[u]; i; i=e[i].nxt) {
int v = e[i].v;
if(v == fa) continue;
dfs(v,u);
add[u] = max(add[u],add[v]);
sub[u] = max(sub[u],sub[v]);
// debug(u);
}
a[u] += add[u] - sub[u];//子节点加减的指加到父节点上
if(a[u] > 0) sub[u] += a[u];//若小大于0就要减
else add[u] -= a[u];//若小于0就要加,注意此时为负数所以还是减法
}
intt main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
F(i,1,n-1) {
int u,v;
cin>>u>>v;
edge(u,v);
edge(v,u);
}
F(i,1,n) cin>>a[i];
dfs(1,0);
cout<<add[1] + sub[1];//答案即为加的次数与减的次数的和
// F(i,1,n) debug(i);
return 0;
}