藍橋杯節點選擇(java)第一道樹形dp分析
阿新 • • 發佈:2018-11-12
藍橋杯 節點選擇
問題描述
有一棵 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[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就是分析某個點的狀態方程,通過遞迴搜尋進行劃分樹。獲得結果。並且這題只有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。。
也可能是因為我比較菜,,想不出好的方法,呵呵,。歡迎大佬踢場。。