CF274B Zero Tree

题目描述

一棵树是一个有n个节点与正好n-1条边的图;并且符合以下条件:对于任意两个节点之间有且只有一条简单路径。

我们定义树T的子树为一棵所有节点是树T节点的子集,所有边是T边的子集的树。

给定一颗有n个节点的树,假设它的节点被编号为1n。每个节点有一个权值,v_{i}表示编号为i的节点的权值。你需要进行一些操作,每次操作符合以下规定:

- 在给定的这棵树中选择一棵子树,并保证子树中含有节点1
- 把这棵子树中的所有节点加上或减去1

你需要计算至少需要多少次操作来让所有的节点的权值归零。 输入数据

第一行包含一个整数n,表示树中节点的数量

接下来的n-1行,一行两个整数u,v,表示u和v之间有一条边(u!=v)

最后一行包含n个整数v_{i}​,用空格隔开,表示每个节点的权值 输出数据

一行一个整数,输出最小需要的操作次数。

输入样例

3
1 2
1 3
1 -1 1

输出样例

3

数据规模

对于30%的数据有n\leqslant 100,|v_{i}|\leqslant 1000

对于50%的数据,n\leqslant10^{4}

对于100%的数据,n\leqslant10^5,|v_{i}|\leqslant10^{9}

分析

1、题目解释

给定一棵树,每次可以选择一个子树(含有节点1),该子树上的每个点的权值 +1或 -1。问最少多少次该树变为全 0

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[i]为需要加的次数,sub[i]为减法的个数,其中i表示当前根。

那么可以得出:

        add[i]=max(add[i],add[v]),sub[i]=max(sub[i],sub[v])  ,v\in i_{son}

初始化:如果a[i]< 0,则sub[i]=|a[i]|(为负数需要加),否则add[i]=a[i]

验证(关于上面的样例)

可以自己下去验证一下:(记得将子节点加减的指加到父节点上)

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;
}

  • 45
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值