B. Zero Tree(思维+树形dp)

题意翻译

题目描述

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

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

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

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

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

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

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

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

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

3
1 2
1 3
1 -1 1

输出样例

3

数据规模 对于30\%30%的数据,n\leq100,|vi|\leq1000n≤100,∣vi∣≤1000

对于50\%50%的数据,n\leq10^4n≤104

对于100\%100%的数据,n\leq10^5,|vi|\leq10^9n≤105,∣vi∣≤109

Translated by 首相大大

输入输出样例

输入 #1复制

3
1 2
1 3
1 -1 1

输出 #1复制

3

题意:每次选的时候一定要包含1,但是子树的大小不定,具体意思就是说选择了一个点,从这个点往上直到根节点1的经过的点都要同时加1/-1;

考虑树形dp,add[x]:x点增加的次数,del[x]:x点减少的次数。

对于父节点,遍历其儿子得出add[x]=max(add[x],add[v]),del[x]=max(del[x],del[v]);

原因在于,比如叶子节点a是7,叶子节点b是5,那么更新的时候,开始叶子节点a和叶子节点b同时更新-1,到了叶子节点b为0了,就单单更新叶子节点a。这样就是说其a和b的父亲节点更新最大的7就可以。那么-7和-5同理。

当叶子节点a是7,b是-5,那么两个点开始同时更新+1或者-1肯定不是最小,对b以及路上的+5,对a以及路上的-7,这样其a和b的父亲节点最终的更新值a[x]+=(add[x]-del[x]),也就是a[x]-=2,然后此时a和b叶子节点都为1,此时父亲节点重复同样的事情。树形dp即可。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+100;
typedef long long LL;
LL a[maxn],add[maxn],del[maxn],ans=0;
vector<LL>g[maxn];
void dfs(LL u,LL fa)
{
	for(LL i=0;i<g[u].size();i++){
		LL v=g[u][i];
		if(v==fa) continue;
		dfs(v,u);
		add[u]=max(add[u],add[v]);
		del[u]=max(del[u],del[v]);	
	}
	a[u]+=add[u]-del[u];
	if(a[u]>0){
		del[u]+=a[u];
	}	
	else{
		add[u]+=(-a[u]);
	}
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n;cin>>n;
  for(LL i=1;i<n;i++){
  	LL x,y;cin>>x>>y;
  	g[x].push_back(y);g[y].push_back(x);
  }
  for(LL i=1;i<=n;i++) cin>>a[i];
  dfs(1,0);
  cout<<add[1]+del[1]<<endl;
return 0;
}

猜你喜欢

转载自blog.csdn.net/zstuyyyyccccbbbb/article/details/108567001