8月30日做的一道顺丰笔试题,当时一点思路都没有,后来在网上查了才知道是codeforces上的原题,归类是树形dp,下面来介绍一下这道题以及Java的解法
题目:给出一棵树,每个点有权值,每次操作可以对一个联通子集中的点全部加1,或者全部减1,且每次操作必须包含点1,问最少通过多少次操作可以让整棵树每个点的权值变为0
输入输出格式:
输入:
3
1 2
1 3
1 -1 1
第一行表示这棵树包含的全部节点数;接下来n-1行表示ai和bi之间有边;最后一行表示顺序的每个节点对于的起始权值
输出:
3
思路:
因为每次修改都必须经过点1,所以将点1作为树根开始dfs
因为1是根,所以每次修改一个点,该点到根路径上的点全部都会被做相同的修改
add[i]表示i点增加的次数
del[i]表示i点减小的次数
a[i]表示i点的值
显然当前点的add和del肯定大于等于子节点的add和del
因为当前节点在子节点到根的路径上,所以子节点的修改一定也会施加到当前点。
所以先对于所有子节点v:add[x]=max(add[v]),del[x]=max(del[v])
先计算一下结果:a[x]+=add[x],a[x]-=del[x]
如果a[x]不为0,则a[x]还需要修改,如果大于0则del[x]+=a[x],否则add[x]+=(-a[x])
一直搜到根点1,最后的答案为add[1]+del[1]
Java代码(网上都是c++的):
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import javafx.scene.layout.CornerRadii;
class Main{
static Map<Integer,List<Integer>> edges;
static int[] add;
static int[] del;
static int[] score;
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
edges=new HashMap<>();
add=new int[n+1];
del=new int[n+1];
score=new int[n+1];
for(int i=1;i<n;i++){
int a=sc.nextInt(); int b=sc.nextInt();
List<Integer> temp1=edges.getOrDefault(a,new ArrayList<>());
temp1.add(b);
edges.put(a, temp1);
List<Integer> temp2=edges.getOrDefault(b,new ArrayList<>());
temp2.add(a);
edges.put(b, temp2);
}
for(int i=1;i<=n;i++){
score[i]=sc.nextInt();
}
dfs(1,-1);
System.out.println(add[1]+del[1]);
}
private static void dfs(int root,int lastp){
List<Integer> list=edges.get(root);
int m1=0,m2=0;
for(int v:list){
if(v==lastp)
continue;
dfs(v,root);
add[root]=Math.max(add[root],add[v]);
del[root]=Math.max(del[root],del[v]);
}
int dif=score[root]+(add[root]-del[root]);
if(dif>0){
del[root]+=dif;
}else{
add[root]-=dif;
}
}
}