1. 程式人生 > >藍橋杯節點選擇(java)第一道樹形dp分析

藍橋杯節點選擇(java)第一道樹形dp分析

藍橋杯 節點選擇

問題描述
    有一棵 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的正整數。

  • 看到藍橋杯的樹形dp題,剛開始看的很矇蔽。從來沒做過樹形的dp,剛開始表示很難理解,當時主要的疑惑點有一下幾點:
    1:這個樹怎麼解決?這麼亂的一棵樹感覺根本無法下手,因為你不知道那個是根節點,那個是子節點。輸出的結果又和那個有關係?
    2:樹形怎麼dp?以前都是遇到線性區間的dp,樹形dp,怎麼查詢連續點,就算找到連續的點交叉點如何處理?
  • 看了一些部落格和文章之後,有了解到dp處理樹形的特殊手段:
    : 一般來說,樹形dp的每個節點都有一個選擇性,本題就是該店選擇和不選擇,dp[i][0]表示不選擇,dp[i][1]表示選擇該節點。
    :對於樹形dp,一般要和搜尋結合(更確切的說是遞迴)結合,對於樹的劃分層次,一般是選擇一個點,然後從這個點進行往下搜尋 ,他的鄰居都變成他的子節點。也就是相當於從這個根節點除法,可能有很多分叉。也就是有很多分叉會到根節點。但是這有和dp有啥關係呢?dp是從尾到頭還是從頭到尾?
    :對於順序的選擇肯定是從尾部到頂部,因為dp要的是一個整合結果而不是分散求最值或者其他。那麼還有問題就是那麼多的跟節點,那麼多合的點,還有反向路徑不好記錄,記錄的難度和開銷都超級大。如果從尾部推到dp還是有一定難度。
    :我們要先拋開整體看區域性這個點,對於某個單點來說(如果他是頭節點),分析到他的結果,如果取他那麼他的兒子們都不能取,那麼dp[i][1]=dp[兒子們][0]+value[i];如果不取他,那麼對於每個兒子來說要給最大的,那麼dp[i][0]=max(dp[每個兒子][0],dp[每個兒子][1]);這樣就得到遞推式dp[x][0]=sum(max(dp[num][0],dp[num][1]));dp[x][1]=sum(dp[num][0])+value[i].
    :對於頭節點(可以任選),滿足④,對於頭節點的兒子也同樣滿足④,但是唯一不同的是要過濾頭節點(準確的說是父節點)。防止死迴圈。遞迴搜尋的過程是雙向的過程,先去再回,我們可以同過先去構造樹並且獲得後面的一些資訊,然後根據後來得到的值進行操作當前等級的dp。
  • 總的來說這個樹形dp就是分析某個點的狀態方程,通過遞迴搜尋進行劃分樹。獲得結果。並且這題只有n-1條路徑,n個點,不用考慮去重。
    附上程式碼:
package 演算法訓練;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.List;
public class 節點選擇 {
    // static boolean jud[];
     static int dp[][];//dp陣列
     static int value[];//儲存權值
     static List <Integer>[]list;//鄰接表儲存圖。節省空間
	public static void main(String[] args) throws IOException {
		// TODO 自動生成的方法存根
		StreamTokenizer in=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		in.nextToken();
		int n=(int)in.nval;
		value=new int[n];//權值
		dp=new int[n][2];
		list=new ArrayList[n];
		
		for(int i=0;i<n;i++) {list[i]=new ArrayList<>();}
		for(int i=0;i<n;i++)
		{
			in.nextToken();
			value[i]=(int)in.nval;
		}
		for(int i=0;i<n-1;i++)
		{
		   in.nextToken();	int t1=(int)in.nval;
			in.nextToken();int t2=(int)in.nval;
			list[t1-1].add(t2-1);//新增路徑
			list[t2-1].add(t1-1);			
		}
		dfs(0,-1);//理論上任意n之內節點都可以,但是右側第一個理論上保證不是這個點的父親
		int value=max(dp[0][0], dp[0][1]);
		out.println(value);
		out.flush();
	}

	private static void dfs(int x, int y) {//當前節點,父親節點
		for(int i=0;i<list[x].size();i++)
		{
			int num=list[x].get(i);
			if(num!=y)//不是父親節點
			{
				dfs(num,x);
				dp[x][0]+=max(dp[num][0],dp[num][1]);
				dp[x][1]+=dp[num][0];
			}
		}
		dp[x][1]+=value[x];//加上自己的權值
		
	}

	private static int max(int i, int j) {
		// TODO 自動生成的方法存根
		return i>=j?i:j;
	}

}

但是結果只能過7個,
在這裡插入圖片描述
看了下其他人沒優化輸入的只能過5個超時。我用測試資料測試了一下原因是棧記憶體溢位。苦逼的Java。。
在這裡插入圖片描述
也可能是因為我比較菜,,想不出好的方法,呵呵,。歡迎大佬踢場。。