回溯法之生命之树

回溯法Q2——生命之树


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

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

「输出格式」
输出一行一个数,表示上帝给这棵树的分数。

「样例输入」
5
1 -2 -3 4 5
4 2
3 1
1 2
2 5

「样例输出」
8

「数据范围」
对于 30% 的数据,n <= 10
对于 100% 的数据,0 < n <= 10^5, 每个节点的评分的绝对值不超过 10^6 。

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗  < 3000ms


这是一颗无向赋权图,要求是找一棵最大的连通块(生成树)。

在这里插入图片描述

package _2015;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
* @author JohnnyLin
* @version Creation Time:2020年10月12日 下午11:04:40
*/
public class _t10生命之树 {
	static int n;
	static long ans;
	//每个数组元素为列表
	static List<Integer> nodeEdge[];
	static long[] value;
	static void dfs(int son,int father) {
		for(int i=0;i<nodeEdge[son].size();i++) {
			int next=nodeEdge[son].get(i);
			if(next!=father) {//相邻结点不是父结点
				//子树最大连通块为负值则不要了 比如说3号结点的子树为负值
				dfs(next, son);
				value[son]+=Math.max(0,value[next]);
				//System.out.println(value[next]);
			}
		}
		//根结点可能是负值
		ans=Math.max(ans,value[son]);
		//System.out.println(ans);

	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		 n=reader.nextInt();
		 value=new long[n+1];
		 nodeEdge=new ArrayList[n+1];
		 for(int i=1;i<=n;i++) {
			 value[i]=reader.nextInt();
			 nodeEdge[i]=new ArrayList<Integer>();
		 }
		 for(int i=1;i<n;i++) {
			 int u=reader.nextInt();
			 int v=reader.nextInt();
			 nodeEdge[u].add(v);
			 nodeEdge[v].add(u);
		 }
		 dfs(1,-1);
		 System.out.println(ans);
	
		 
		 

	}

}

另一种写法:
参考了网上代码的

package 第六届;

import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

public class 生命树_网友版 {
	static int[] nodeValue;//存放各个点的和谐值
	static List<Integer>[] point;//存放各个点的包含的邻接边
	static int[] value;//存放以某个点为根节点情况下的最大和谐值和
	static int ans=0;//记录最大结果
	static void dfs(int son,int father) {
		value[son]=nodeValue[son];//以son为根节点,一开始时其最大和谐值为它本身
		for(int i=0;i<point[son].size();i++) {//枚举与son相连的结点
			int next=point[son].get(i);
			//当枚举的下一个结点是son先前经过的点father 跳过 防止绕圈
			if(next==father)continue;//如果都continue掉说明这个点是叶子结点
			dfs(next,son);
			//回溯(子节点平行状态都走完没得走了或者说状态没得转移了才会回溯)得到父节点与子节点和
			if(value[next]>0)//子节点的和谐值
				value[son]+=value[next];
			ans=Math.max(ans, value[son]);
		}
		
	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		int n=reader.nextInt();
		nodeValue=new int [n+1];
		value=new int[n+1];
		point=new LinkedList[n+1];
		for(int i=1;i<=n;i++) {
			nodeValue[i]=reader.nextInt();
			point[i]=new LinkedList();
		}
		for(int i=1;i<n;i++) {
			int x=reader.nextInt();
			int y=reader.nextInt();
			point[x].add(y);
			point[y].add(x);
		}
		dfs(1,-1);//从0号结点开始搜索 假设它的父节点为-1可以为其他的无关量
		System.err.println(ans);
	}

}

注意:java里调用栈最多一万层,因此使用dfs最多可以通过n <= 10,30%的数据

回溯法Q3——抽签

 /*题目描述
X星球要派出一个5人组成的观察团前往W星。
其中:
A国最多可以派出4人。
B国最多可以派出2人。

C国最多可以派出2人。

D国最多可以派出1人。

E国最多可以派出1人。

F国最多可以派出3人。
那么最终派往W星的观察团会有多少种国别的不同组合呢?

下面的程序解决了这个问题。
数组a[] 中既是每个国家可以派出的最多的名额。
程序执行结果为:
DEFFF
CEFFF
CDFFF
CDEFF
CCFFF
CCEFF
CCDFF
CCDEF
BEFFF
BDFFF
BDEFF
BCFFF
BCEFF
BCDFF
BCDEF
....

(以下省略,总共101行)

*/
package 排列组合类;

public class 抽签 {
	//数组a[]表示每个国家可以派出的最多名额 0-a[k]
	static int a[]={4,2,2,1,1,3};
	static int ans;
	static void dfs(int index,int cnt,String s) {
		//当前选a[index]类 还有cnt个名额 当前已经选的人有s
		System.out.println(index+" "+cnt+" "+s);
		
		if(cnt<0) {return;}
		
		if(index==6) {
			if(cnt==0) {
				ans++;
				System.out.println(s);
			}
			return;
		}
		//{4,2,2,1,1,3}
		//{AAAA,BB,CC,D,E,FFF}
		String s2=s;
		for(int i=0;i<=a[index];i++) {
			dfs(index+1,cnt-i,s2);
			s2=s2+(char)('A'+index);
		}
		
	}

	public static void main(String[] args) {
		dfs(0,5,"");
		System.out.println(ans);

	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值