路径相关树形dp——卖树

路径相关树形dp——卖树

问题描述

小蓝和小桥是两位花园爱好者,她们在自己的花园里种了一棵n个节点的树,每条边的长度为k。初始时,根节点为1号节点。她们想把这棵树卖掉,但是想卖个好价钱。树的价值被定义为根节点到所有节点的路径长度的最大值。

为了让这棵树更有价值,小蓝和小桥可以对这棵树进行一种操作: 花费C的代价,将根节点从当前的节点移动到它的一个相邻节点上。注意,这个操作不会改变树的形态,只是改变了根节点的位置。

她们希望通过尽可能地进行操作,使得卖出去的这棵树的盈利最大。盈利被定义为卖出去的树的价值减去操作的总代价。

请你帮助她们,找出她们能够获得的最大盈利。

输入格式

第一行包含一个整数t,表示测试数据组数。

每组数据第一行包含三个整数n、 k和c,表示树的节点数、每条边的长度和进行一次移动操作的代价。接下来n- 1行,每行描述树的一条边,包含两个整数 u i u_i ui v i v_i vi,表示树中连接 u i u_i ui v i v_i vi之间的一条边。

输出格式

对于每组数据,输出一个整数,表示最大盈利。

数据规模

对于100%的评测数据, 1 ≤ t ≤ 20 , 2 < n ≤ 1 0 5 , 1 ≤ k , c ≤ 1 0 9 , 1 ≤ u i , v i ≤ n 1≤t≤20,2< n≤10^5,1≤k,c≤10^9,1≤u_i,v_i≤n 1t20,2<n105,1k,c109,1ui,vin

样例输入

4
3 2 3
2 1
3 1
5 4 1
2 1
4 2
5 4
3 4
6 5 3
4 1
6 1
2 6
5 1
3 2
10 6 4
1 3
1 9
9 7
7 6
6 4
9 2
2 8
8 5
5 10

样例输出

2
12
17
32
题目分析

与路径相关的树形dp题的思路,这里就不能用常规的板子了。这里用到了树的直径的知识点,首先了解一下树的直径的定义。

树的直径是树上最长的一条链,这条链并不是唯一的。我们可以画一下,看一看树的直径有什么样的特点。

路径相关树形dp——卖树_第1张图片

如图所示,直径由树中的两个节点u和v决定,可以写作(u,v)其中u和v具有以下性质:

性质1:u和v的度数均为1。

性质2:在以任意一个点为根的树上,u和v中必然存在一个点作为最深的叶子节点。

对于性质1,假设u或者v不是度数为1的节点,说明u或者v还可以继续向下走,那么也就是说路径的长度还能增加,这和当前u走到v的路径是直径矛盾。所以u和v必然是度为1的节点。

对于性质2,假设u和v都不是最深的那个叶子节点,并且最深的叶子节点是w,根节点用r表示,u到v的路径是u-r-v,w到v的路径是w-r-v,因为w是最深的那个叶子节点,所以w到根节点的距离一定大于u到根节点的距离,即u-r的距离小于w-r的距离,此时u到v之间的路径就不是最长路径,和u走到v的路径是直径矛盾。所以u和v必然存在一个点作为最深的叶子节点。

回到题目,我们可以考虑求以每个节点为根节点时树的价值,假设用数组dis[i]表示以i为根节点时树的价值。将节点i变成根节点要花费的代价时节点i的深度,用dep[i]表示节点i的深度。那么将节点i作为根节点的盈利就是 d i s [ i ] − d e p [ i ] ∗ c dis[i]-dep[i]*c dis[i]dep[i]c。注意一般我们在算深度或者长度时每条边的权值都是1,但是本题给了每条边的权值是k,所以盈利应该变成 d i s [ i ] ∗ k − d e p [ i ] ∗ c dis[i]*k-dep[i]*c dis[i]kdep[i]c

dep[i]表示的是节点i的深度。可以在一次深度搜索的过程中求出。但是dis[i]应该如何求呢?这里就要利用树的直径。假设我们已经求出树的直径为(u,v),那么根据树的直径的性质2,当i作为树的根节点时,u或者v必然是距离i最远的一个子节点。

接下来考虑如何求树的直径。分为两次dfs遍历,因为u和v必然有一个是当前节点为根节点时最深的那个节点,那么通过第一次dfs遍历,求出以当前节点为根节点时,每个节点对于的深度,那么深度最大的那个节点必然是构成直径的节点之一。代码如下,

dfs(1,0);
int u = 1;
for(int i = 1;i <= n;i++) {
    if(dis[u]<dis[i]) u = i;
}

u表示的是构成直径的节点之一。此时再以u节点为根节点进行一次dfs遍历,求每个节点此时的深度,深度最深的节点即为另一个构成直径的节点。代码如下,

Arrays.fill(dis, 0);
dfs(u, 0);
int v = 1;
for(int i = 1;i <= n;i++) {
    dis2[i] = dis[i];
    if(dis[v]<dis[i]) v = i;
}

那么 d i s 2 dis2 dis2记录的是节点u和每个节点之间的距离,但是节点u并不一定是某个节点为根时最深的节点,还有可能是节点v。所以需要以节点v为根再进行一次dfs遍历。由于每次dfs遍历的过程中都是改变的 d i s dis dis数组,所以需要用 d i s 2 dis2 dis2数组暂存一下当前的遍历结果。遍历完后假设以节点u为根求得的节点深度为 d i s 2 [ i ] dis2[i] dis2[i],以节点v为根求得的节点深度为 d i s [ i ] dis[i] dis[i],那么以节点i为根时最深的深度为 d i s = m a x ( d i s [ i ] , d i s 2 [ i ] ) dis=max(dis[i],dis2[i]) dis=max(dis[i],dis2[i])代码如下,

Arrays.fill(dis,0);
dfs(v, 0);
for(int i = 1;i <= n;i++) {
    dis[i] = Math.max(dis[i], dis2[i]);
}

那么最终的答案就是在枚举每个节点为根的情况下求所得盈利,也就是一开始说的 d i s [ i ] ∗ k − d e p [ i ] ∗ c dis[i]*k-dep[i]*c dis[i]kdep[i]c

题目代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;

public class Main {
	static HashMap<Integer, List<Integer>> map = new HashMap<Integer, List<Integer>>();
	static long dis[],dis2[];
	static long dep[];
public static void main(String[] args) {
	Scanner scanner = new Scanner(System.in);
	int t = scanner.nextInt();
	while(t-- > 0) {
		int n = scanner.nextInt();
		int k = scanner.nextInt();
		int c = scanner.nextInt();
		map.clear();
		dis = new long[n+1];
		dis2 = new long[n+1];
		dep = new long[n+1];
		Arrays.fill(dis,0);
		Arrays.fill(dep,0);
		for(int i = 1;i < n;i++) {
			int u = scanner.nextInt();
			int v = scanner.nextInt();
			add(v,u);
			add(u,v);
		}
		dfs(1,0);
		int u = 1;
		long ans = 0;
		for(int i = 1;i <= n;i++) {
			dep[i] = dis[i];
			if(dis[u]<dis[i]) u = i;//求直径的某一个端点
		}
		Arrays.fill(dis, 0);
		dfs(u, 0);
		int v = 1;
		for(int i = 1;i <= n;i++) {
			dis2[i] = dis[i];
			if(dis[v]<dis[i]) v = i;//求直径的令一个端点
		}
		Arrays.fill(dis,0);
		dfs(v, 0);
		for(int i = 1;i <= n;i++) {
			dis[i] = Math.max(dis[i], dis2[i]);//求以某个节点为根节点时最深的深度
		}
		for(int i = 1;i <= n;i++) {
			ans = Math.max(ans, dis[i]*k-c*dep[i]);
		}
		System.out.println(ans);
	}
	
}
private static void dfs(int u, int fa) {
	// TODO Auto-generated method stub
	if(map.get(u)==null) return;
	for(Integer e:map.get(u)) {
		if(e==fa) continue;
		dis[e]=dis[u]+1;
		dfs(e, u);
	}
}
private static void add(int v, int u) {
	// TODO Auto-generated method stub
    if(!map.containsKey(v)) map.put(v, new ArrayList<Integer>());
    map.get(v).add(u);
}
}

你可能感兴趣的:(蓝桥杯,算法,java,动态规划)