节点选择
问题描述
有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?
输入格式
第一行包含一个整数 n 。
接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。
接下来一共 n-1 行,每行描述树上的一条边。
输出格式
输出一个整数,代表选出的点的权值和的最大值。
样例输入
5
1 2 3 4 5
1 2
1 3
2 4
2 5
样例输出
12
样例说明
选择3、4、5号点,权值和为 3+4+5 = 12 。
数据规模与约定
对于20%的数据, n <= 20。
对于50%的数据, n <= 1000。
对于100%的数据, n <= 100000。
权值均为不超过1000的正整数。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.StringTokenizer;
import java.util.Arrays;
public class Main {
public static void main(String[]args) throws Exception{
//测试运行时间
//long Start = System.nanoTime();
//读取输入并构造整颗树
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
Node[] arr = new Node[n + 1];
st = new StringTokenizer(br.readLine());
for(int i = 1;i <= n;i++){
arr[i] = new Node();
arr[i].setNow(i);
arr[i].setData(Integer.parseInt(st.nextToken()));
}
ArrayList<Integer>[] aList = new ArrayList[n + 1];
for(int i = 1; i <= n;i++){
aList[i] = new ArrayList<Integer>();
}
int[] list = new int[n + 1];
list[0] = 1;
Queue<Integer[]> s = new LinkedList<Integer[]>();
int end = 0;
for(int i = 1; i < n;i++){
st = new StringTokenizer(br.readLine());
int start = Integer.parseInt(st.nextToken());
int next = Integer.parseInt(st.nextToken());
if(list[next] == 1){
list[start] = 1;
aList[next].add(start);
}
else if(list[start] == 1){
list[next] = 1;
aList[start].add(next);
}
else if(list[start] == 2){
list[next] = 1;
aList[start].add(next);
}
else if(list[next] == 2){
list[start] = 1;
aList[next].add(start);
}
else{
if(i != 1){
Integer[] list1 = new Integer[2];
list1[0] = start;
list1[1] = next;
s.add(list1);
}
else{
end = start;
list[start] = 2;
list[next] = 1;
aList[start].add(next);
}
}
}
br.close();
while(s.size() != 0){
Integer[] list1 = s.peek();
int start = list1[0];
int next = list1[1];
if(list[next] == 1){
list[start] = 1;
aList[next].add(start);
}
else if(list[start] == 1){
list[next] = 1;
aList[start].add(next);
}
else{
s.add(list1);
}
s.poll();
}
for(int i = 1;i <= n;i++){
arr[i].setNext(aList[i].stream().mapToInt(k -> k).toArray());
}
//填入各个结点所处树的高度
arr[end].setHeight(1);
int h = 2;
ArrayList<Integer> alist = new ArrayList<Integer>();
alist.add(end);
while(alist.size() != 0){
ArrayList<Integer> alistTemp = new ArrayList<Integer>();
for(Integer flag:alist){
for(int i:arr[flag].getNext()){
arr[i].setHeight(h);
alistTemp.add(i);
}
}
h++;
alist = alistTemp;
}
//打印结点信息
//for(int i = 1;i < arr.length;i++){
// System.out.println(arr[i].toString());
//}
//此处h在上述循环中在最后的两次循环多家两次,在此减取,此时h为树的高度
h = h - 2;
//max[]以各个结点为根结点的树的最大权值
int[]max = new int[n + 1];
for(int a = h;a >= 1;a--){
for(int i = 1;i <= n;i++){
//从树底向上计算以各个结点为根节点的树的最大权值
if(arr[i].getHeight() == a){
if(a == h){
max[i] = arr[i].getData();
}
else if(a == h - 1){
for(int j:arr[i].getNext()){
max[i] += max[j];
}
max[i] = max[i] < arr[i].getData()?arr[i].getData():max[i];
}
else{
int max2 = 0;
int max3 = 0;
for(int j:arr[i].getNext()){
max2 += max[j];
for(int k:arr[j].getNext()){
max3 += max[k];
}
}
max[i] = max2 < arr[i].getData() + max3?arr[i].getData() + max3:max2;
}
if(a == 1){
//打印树的根结点的最大权值,即为最大权值
System.out.println(max[i]);
}
}
}
}
//打印测试时间
//long End = System.nanoTime();
//System.out.println((End - Start)/Math.pow(10,9));
}
}
class Node{
private int now;
private int[] next;
private int data;
private int height = Integer.MAX_VALUE;
public void setNow(int now){
this.now = now;
}
public int getNow(){
return now;
}
public void setNext(int[] next){
this.next = next;
}
public int[] getNext(){
return next;
}
public void setData(int data){
this.data = data;
}
public int getData(){
return data;
}
public void setHeight(int height){
this.height = height;
}
public int getHeight(){
return height;
}
@Override
public String toString(){
return "{now:" + now + ",next:" + Arrays.toString(next) + ",data:" + data + ",height:" + height + "}";
}
}
思路
算最大权值即计算根结点的权值。而计算根结点的权值与以下一层的结点为根结点的树的最大权值和以第三层的结点为根结点的树的最大权值有关。
以上图为例,计算最大权值即计算以1为根节点的最大权值,此时有两种情况:
- 选择1号结点,此时第二层的结点都无法选择,只能从第三层结点开始计算以第三层结点为根结点的树的最大权值,此时最大权值即为根结点1号的权值加上以第三层(4,5,6)为根结点的树的最大权值所有之和。
- 不选择1号结点,此时第二层结点可选,此时最大权值即为以第二层(2,3)为根结点的树的最大权值所有之和。
由上诉思路可以看出可以使用动态规划,由树底开始计算以各个结点为根结点的最大权值,并向上计算,直到算出出根结点的最大权值即为最大权值。
时间超时问题
猜测:上述算法默认选择第一条边的第一个结点为根结点构造树,此时可能出现树的不平衡,可能会导致时间超时,并未测试,尚未解决。