標籤:二叉樹 刪掉 初始化 namespace getchar ++ 個數 方便 返回
題目描述
有一棵蘋果樹,如果樹枝有分叉,一定是分2叉(就是說沒有只有1個兒子的結點)
這棵樹共有N個結點(葉子點或者樹枝分叉點),編號為1-N,樹根編號一定是1。
我們用一根樹枝兩端串連的結點的編號來描述一根樹枝的位置。下面是一顆有4個樹枝的樹
2 5 \ / 3 4 \ / 1 現在這顆樹枝條太多了,需要剪枝。但是一些樹枝上長有蘋果。
給定需要保留的樹枝數量,求出最多能留住多少蘋果。
輸入輸出格式
輸入格式:
第1行2個數,N和Q(1<=Q<= N,1<N<=100)。
N表示樹的結點數,Q表示要保留的樹枝數量。接下來N-1行描述樹枝的資訊。
每行3個整數,前兩個是它串連的結點的編號。第3個數是這根樹枝上蘋果的數量。
每根樹枝上的蘋果不超過30000個。
輸出格式:
一個數,最多能留住的蘋果的數量。
輸入輸出範例
輸入範例#1:
5 2
1 3 1
1 4 10
2 3 20
3 5 20
輸出範例#1:
21
演算法:
樹形DP
分析:
這道題其實是要我們計算在一棵二叉樹上保留一定數量的枝條情況下的最大權值。
這是一個基礎的樹形DP模板題,在二叉樹上進行動規。我們發現對於樹上的任意一棵子樹,它的根節點和兩個子樹存在著關係,我們探究一下,樹是由遞迴性質得到的,所以我們首先要把我們得到的資料先在一個數組上建造出來。
把樹建好了之後,我們就可以探究它的狀態轉移方程,我們已知n-1條邊,為了方便,我們把它設為雙向的邊。我設len[x][y]和len[y][x]表示x到y的邊上權值為len[x][y](或len[y][x])。
接下來,設num[v]表示以v為子節點的那條邊的長度,設f[v][k]為以v為根節點的子樹中保留k條邊的最大權值,設tr[v][ans]為以v為根節點的左右兒子分別是什麼,當ans表示1時,他是左兒子,反之,則為右兒子。
把一棵樹放到一個線性DP裡面想的話,就會容易多了。我要求當前的f[v][k],我首先要求得他的左右兒子分擔一共k條邊的最大值分別是什麼,然後相加即可。
可列得狀態轉移方程為:f[v][k]=max(f[v][k],f[tr[v][1]][i]+f[tr[v][2]][k-i-1]+num[v]);
這個東西相信不難看懂,接下來我們需要最佳化程式。既然樹可以遞迴來建造,那麼我們是不是也可以用遞迴來求解呢?嗯,是可以的。
這是我們就可以將普通的DP放到DFS上做,合成了記憶化搜尋。因為有一些枝條我在之前已經計算過了,那麼我可以直接拿來用。
說幾點注意的,最後輸出答案要輸出q+1的情況,因為在計算過程中我們為了方便,將邊的問題都轉化成父節點或者是子節點問題,所以需要我們在節點數上再+1。
切記len數組要初始化一個負數,在用完其中一條邊時,記得把那條邊和另外一條等價的邊都給刪掉。
上代碼:
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 5 int n,q,tr[110][3],num[110],len[110][110],f[110][110]; //資料規模不大,int 型已經足夠 6 7 inline int read() //讀入最佳化 8 { 9 int x=0,f=1;10 char c=getchar();11 while (c<48||c>57)12 f=c==‘-‘?-1:1,c=getchar();13 while (c>=48&&c<=57)14 x=(x<<1)+(x<<3)+(c^48),c=getchar();15 return x*f;16 }17 18 void buildtree(int v) //建樹19 {20 int i,ans=0;21 for (i=1;i<=n;i++)22 if (len[v][i]>=0) //有分叉23 {24 ans++; //記錄並判斷左右子樹25 tr[v][ans]=i;26 num[i]=len[v][i]; //記錄長度27 len[v][i]=len[i][v]=-1; //刪除兩條邊28 buildtree(i); //遞迴它的孩子節點29 if (ans==2) //到了就返回30 return;31 }32 }33 34 void dfs(int v,int k) //記憶化搜尋35 {36 if (k==0) //邊界37 f[v][k]=0;38 else39 if (tr[v][1]==0&&tr[v][2]==0) //到了葉子節點,其值等於原值40 f[v][k]+=num[v];41 else42 {43 f[v][k]=0; //先清零44 int i,j;45 for (i=0;i<k;i++) //枚舉0到k-1的情況46 {47 if (!f[tr[v][1]][i]) //左兒子記憶化48 dfs(tr[v][1],i);49 if (!f[tr[v][2]][k-i-1]) //右兒子記憶化50 dfs(tr[v][2],k-i-1);51 f[v][k]=max(f[v][k],f[tr[v][1]][i]+f[tr[v][2]][k-i-1]+num[v]); //狀態轉移52 }53 }54 }55 56 int main()57 {58 int i,j;59 n=read();60 q=read();61 for (i=1;i<=n;i++) //初始化62 for (j=1;j<=n;j++)63 len[i][j]=-1;64 for (i=1;i<=n-1;i++)65 {66 int x=read(),y=read();67 len[x][y]=len[y][x]=read();68 }69 buildtree(1); //建樹70 dfs(1,q+1); //求解71 printf("%d",f[1][q+1]); //注意,切記是q+172 return 0;73 }
這是一道二叉樹的基礎DP,我感覺挺好理解的,主要的模型就是遞迴建樹+記憶化搜尋DP,簡單記為2個DFS。
嗯,就這樣了。
【洛穀P2015】二叉蘋果樹