深度优先搜索之生命之树

1. 问题描述:

在X森林里,上帝创建了生命之树。 他给每棵树的每个节点(叶子也称为一个节点)上,都标了一个整数,代表这个点的和谐值。 上帝要在这棵树内选出一个非空节点集S,使得对于S中的任意两个点a,b,都存在一个点列 {a, v1, v2, ..., vk, b} 使得这个点列中的每个点都是S里面的元素,且序列中相邻两个点间有一条边相连。 
在这个前提下,上帝要使得S中的点所对应的整数的和尽量大。 这个最大的和就是上帝给生命之树的评分。  
经过atm的努力,他已经知道了上帝给每棵树上每个节点上的整数。但是由于 atm 不擅长计算,他不知道怎样有效的求评分。他需要你为他写一个程序来计算一棵树的分数。   

「输入格式」 
第一行一个整数 n 表示这棵树有 n 个节点。 第二行 n 个整数,依次表示每个节点的评分。 
接下来 n-1 行,每行 2 个整数 u, v,表示存在一条 u 到 v 的边。由于这是一棵树,所以是不存在环的。   
「输出格式」 
输出一行一个数,表示上帝给这棵树的分数。   
「样例输入」 


1 -2 -3 4 5

4 2

3 1

1 2

2 5   

「样例输出」

 8   

2. 思路分析:

① 仔细阅读题目之后我们知道这个非空点集中的所有点都是联通的,也可以这样理解在S这棵树中求解出一部分子树使得这部分子树中的加起来的和是最大的

② 对于这一类的题目,我们一开始是不知道它的具体的权重是多少的,所以我们需要在获取输入的数据之后进行试探尝试每一种的可能性,找到一个加起来的和是最大的部分子树,所以我们可以使用深度优先搜索来解决

③ 但是仔细分析我们可以知道这是一棵无根的树,即谁作为根都是可以的,关键是需要找到最大的权重的那部分,问题是我们需要搜索所有的节点作为根节点的权重吗,我们可以通过分析具体的测试用例,画出以不同的节点作为根看下一最终求得的最大和是不是一样的,通过计算可以发现求得最大的和是一样的,所以树中的节点谁作为根都是可以的,对结果没有什么影响,我们也可以这样想,谁作为根只是节点之间的相互位置变化了而已,但是我们求解的最大结果应该是一样的

④ 因为节点之间是可以互通的,所以需要建立节点之间的双向联系,这里可以创建集合数组来进行连接,在输入数据之后进行双向的标记

static List<Integer>[] list;
list = new ArrayList[n + 1];
for(int i = 0; i < n - 1; i++){
	int a = sc.nextInt();
	int b = sc.nextInt();
	//进行双线的建立联系
	list[a].add(b);
	list[b].add(a);
}

⑤ 在递归的过程中我们假如发现当前节点的子树是大于零的,我们就把子节点的价值累加到当前的节点,并且在整个for循环的递归完成之后那么这个时候以当前节点的所有子节点已经遍历完,而且得到了当前节点的价值,此时需要判断一下当前节点所构成的部分子树与历史上记录的最大和的大小关系来决定是否更新最大和,整个for循环结束表示的意思是当前节点的下的所有节点已经遍历完了

当当前节点的子树大于零我们才将其累加到自身,这是一个很重要的点,我们为什么不写具有返回值的深搜呢?关键也在这里因为当前的传进来的根节点中累加到它的子树中有可能不是最大的,所以这个时候的返回值是没有什么意义的,有可能在树的某一部分所构成的子树和是最大的

⑤ 因为有联系的节点是相互联通的,所以我们为了避免在深搜往下搜索的过程中又回到之前遍历的节点进行遍历,所以我们需要进行标记一下,这里使用的方法是在dfs的方法中多传进来一个参数来,这个参数表示的是当前节点的父节点(另外一个节点是当前的节点),这样它在往下遍历的过程中就不会回到之前的父节点进行遍历,这也是在树的遍历中经常用到的一个小技巧

但是使用深度优先搜索来解决的话由于数据规模太大了而Java中对于栈的调用次数是有限制的,数据太大的话会导致堆栈溢出的情况,所以部分大的测试数据是通不过的

3. 代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
	static int n;
	static int val[];
	static int ans = 0;
	//使用集合数组来建立邻接表可以建立节点之间的双向联系
	static List<Integer>[] list;
	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		list = new ArrayList[n + 1];
		val = new int[n + 1];
		init();
		for(int i = 1; i <= n; i++){
			val[i] = sc.nextInt();
		}
		//节点之间的联系可以使用集合数组来建立联系
		for(int i = 0; i < n - 1; i++){
			int a = sc.nextInt();
			int b = sc.nextInt();
			//进行双向的建立联系
			list[a].add(b);
			list[b].add(a);
		}
		dfs(1, 0);
		System.out.println(ans);
		sc.close();
	}
	
	private static void init() {
		for(int i = 0; i < n + 1; i++){
			list[i] = new ArrayList<Integer>();
		}
	}

	private static void dfs(int u, int fa) {
		for(int i = 0; i < list[u].size(); i++){
			int child = list[u].get(i);
			if(child == fa) continue;
			dfs(child, u);
			if(val[child] > 0){
				val[u] += val[child];
			}
		}
		if(val[u] > ans){
			ans = val[u];
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值