總結:樹形dp的題目,若是單純給出節點間矛盾關係,或者在二叉樹上作揹包,都是比較簡單的。我目前遇到的題目中,多叉樹揹包是一個難點,但願本身能繼續學習,能拿下。OK,下午二分圖最大流搞起!
前言:按照計劃,昨天應該是完成樹形DP7題和二分圖、最大流基礎專題,可是因爲我智商實在拙計,一直在理解樹形DP的思想,因此第二個專題只能順延到今天了。可是昨天把樹形DP弄了個5成懂我是很高興的!下面我把這7題的解題思想和部分代碼分享給你們。ios
題目一:皇宮看守
問題描述:
太平王世子事件後,陸小鳳成了皇上特聘的御前一品侍衛。
皇宮以午門爲起點,直到後宮嬪妃們的寢宮,呈一棵樹的形狀;某些宮殿間能夠互相望見。大內保衛森嚴,三步一崗,五步一哨,每一個宮殿都要有人全天候看守,在不一樣的宮殿安排看守所需的費用不一樣。
但是陸小鳳手上的經費不足,不管如何也無法在每一個宮殿都安置留守侍衛。算法
編程任務:
幫助陸小鳳佈置侍衛,在看守所有宮殿的前提下,使得花費的經費最少。編程
數據輸入:
輸入文件中數據表示一棵樹,描述以下:
第1行 n,表示樹中結點的數目。
第2行至第n+1行,每行描述每一個宮殿結點信息,依次爲:該宮殿結點標號i(0<i<=n),在該宮殿安置侍衛所需的經費k,該邊的兒子數m,接下來m個數,分別是這個節點的m個兒子的標號r1,r2,…,rm。
對於一個n(0 < n<=1500)個結點的樹,結點標號在1到n之間,且標號不重複。數組
數據輸出:
輸出文件僅包含一個數,爲所求的最少的經費。數據結構
樣例輸入:
6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0ide
樣例輸出:
25學習
分析:原本這套題裏還有一個題目叫「戰略遊戲」,抽象後的模型跟這題是同樣的,遇到那題的時候我沒太理解題解是怎麼用樹形DP作的,因而我就跳過了。後來遇到這題發現不研究的話不行。題目說的很清楚,用最少的點覆蓋全部的點,(二分圖裏有一個模型叫用最少的點覆蓋全部的邊,之後再議)。若是是一個圖的話,它是個NP徹底問題,但題目給出的是個樹,避免了後效性的問題,因此能夠用動態規劃來解決。
給出以下定義:spa
轉移以下:
一、由F[i,0]定義可知,設j爲i的兒子節點,至少要有一個i的兒子節點是放置守衛的,其他的兒子節點可放可不放,但因爲根節點i不放,因此其他的兒子節點若是不放的話,必須保證能被觀察到,即F[j][0];因此咱們須要枚舉必須放置的兒子節點,下面的轉移方程描述的很清楚:設計
二、由F[i,1]定義可知,i能夠被觀察到也能夠不被觀察到,但兒子節點必須都要被觀察到,轉移以下:3d
三、由F[i,2]定義可知,i點放置了守衛,因此對於每一個兒子節點都能被觀察到,取F[j,0],F[j,1],F[j,2]最小值便可:
看了題解我是恍然大悟啊,智商不夠,這個轉移方程仍是比較難想的。
參考代碼:
1 // 2 // 皇宮看守.cpp 3 // 樹形DP 4 // 5 // Created by TimmyXu on 13-8-3. 6 // Copyright (c) 2013年 TimmyXu. All rights reserved. 7 // 8 9 #include <iostream> 10 #include <algorithm> 11 #include <cstdio> 12 #include <cstring> 13 #include <string> 14 15 using namespace std; 16 17 const int maxn = 1500+10; 18 19 int f[maxn][3],data[maxn],n,son[maxn][maxn],len[maxn],du[maxn],x,root; 20 21 void doit(int x) 22 { 23 if (len[x] == 0) 24 { 25 f[x][0] = f[x][2] = data[x]; 26 f[x][1] = 0; 27 return; 28 } 29 for (int i = 1;i <= len[x];i++) 30 doit(son[x][i]); 31 f[x][0] = INT_MAX; 32 for (int i = 1;i <= len[x];i++) 33 { 34 int tmp = 0; 35 for (int j = 1;j <= len[x];j++) 36 if (i!=j) 37 tmp += min(f[son[x][j]][0],f[son[x][j]][2]); 38 f[x][0] = min(f[x][0],tmp+f[son[x][i]][2]); 39 } 40 f[x][1] = 0; 41 for (int i = 1;i <= len[x];i++) 42 f[x][1] += min(f[son[x][i]][0],f[son[x][i]][2]); 43 f[x][2] = data[x]; 44 for (int i = 1;i <= len[x];i++) 45 f[x][2] += min(f[son[x][i]][0],min(f[son[x][i]][1],f[son[x][i]][2])); 46 } 47 48 int main() 49 { 50 scanf("%d",&n); 51 memset(data,0,sizeof(data)); //存每一個點放置守衛的代價 52 memset(f,0,sizeof(f)); //dp數組,F[i,j]含義如上述分析 53 memset(du,0,sizeof(du)); //存儲每一個點的入度,用於找出根節點 54 memset(son,0,sizeof(son)); //存儲每一個點的兒子節點 55 memset(len,0,sizeof(len)); //存儲每一個點兒子節點個數 56 for (int i = 1;i <= n;i++) 57 { 58 scanf("%d",&x); 59 scanf("%d%d",&data[x],&len[x]); 60 for (int j = 1;j <= len[x];j++) 61 { 62 scanf("%d",&son[x][j]); 63 du[son[x][j]]++; 64 } 65 } 66 for (int i = 1;i <= n;i++) 67 if (du[i] == 0) 68 { 69 root =i; 70 break; 71 } 72 doit(root); 73 printf("%d\n",min(f[root][0],f[root][2])); 74 }
題目二:選課(Vijos1180)
【問題描述】
大學裏實行學分。每門課程都有必定的學分,學生只要選修了這門課並考覈經過就能得到相應的學分。學生最後的學分是他選修的各門課的學分的總和。
每一個學生都要選擇規定數量的課程。其中有些課程能夠直接選修,有些課程須要必定的基礎知識,必須在選了其它的一些課程的基礎上才能選修。例如,《數據結構》必須在選修了《高級語言程序設計》以後才能選修。咱們稱《高級語言程序設計》是《數據結構》的先修課。每門課的直接先修課最多隻有一門。兩門課也可能存在相同的先修課。爲便於表述每門課都有一個課號,課號依次爲1,2,3,……。下面舉例說明
課號 先修課號 學分
1 無 1
2 1 1
3 2 3
4 無 3
5 2 4
上例中1是2的先修課,即若是要選修2,則1一定已被選過。一樣,若是要選修3,那麼1和2都必定已被選修過。
學生不可能學完大學所開設的全部課程,所以必須在入學時選定本身要學的課程。每一個學生可選課程的總數是給定的。如今請你找出一種選課方案,使得你能獲得學分最多,而且必須知足先修課優先的原則。假定課程之間不存在時間上的衝突。
【輸入格式】
輸入文件的第一行包括兩個正整數M、N(中間用一個空格隔開)其中M表示待選課程總數(1≤M≤1000),N表示學生能夠選的課程總數(1≤N≤M)。
如下M行每行表明一門課,課號依次爲1,2……M。每行有兩個數(用一個空格隔開),第一個數爲這門課的先修課的課號(若不存在先修課則該項爲0),第二個數爲這門課的學分。學分是不超過10的正整數。
【輸出格式】
輸出文件第一行只有一個數,即實際所選課程的學分總數。如下N行每行有一個數,表示學生所選課程的課號。
【輸入樣例】choose.in
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
【輸出樣例】choose.out
13
2
6
7
3
分析:這是一個樹形揹包,在《揹包九講》裏面,這種題型被稱爲泛化物品。用於求解多叉樹上的揹包問題。
徐持衡大牛在2009年國家隊論文《淺談幾類揹包題》一文中給出了一種O(n*C)的算法,我昨天痛苦的理解了一天(仍然是智商不夠)估計本身才理解了五成。下面我就說說個人理解。
F[i,m]表示以i爲根的子樹被分配到m的容量所能得到的最大得分。
對於i的每個兒子j,先將F[i,0~m-v[j]]所有賦值給F[j,0~m-v[j]],按個人理解,就是留出j的空間(由於必定要放),後剩下的m-v[j]個空間的最優值賦值給以j爲根的子樹去進行遞歸計算,遞歸計算完j後,F[i,m] = max(F[i,m],F[j,m-v[j]]+c[j]),v[j]<=m<=M,按個人理解,計算完j子樹後,由於F[j,m]的最優值是包含了F[i,m]的最優值計算的,也就是包含了j以前的i的兒子計算出來的最優值,因此只須要在F[i,m]和F[j,m-v[j]]+c[j](放j結點)中取最大值代替F[i,m]便可。
以上就是我昨天一成天思考的結果。或許我理解的不對,或許在大牛眼裏這很easy,不過對我來講已是個很大的進步啦~
參考代碼:
1 // 2 // 選課.cpp 3 // 樹形DP 4 // 5 // Created by TimmyXu on 13-8-3. 6 // Copyright (c) 2013年 TimmyXu. All rights reserved. 7 // 8 9 #include <iostream> 10 #include <algorithm> 11 #include <cstdio> 12 #include <cstring> 13 #include <string> 14 15 using namespace std; 16 17 const int maxn = 1000+10; 18 19 int x,n,m,data[maxn],g[maxn][maxn],f[maxn][maxn]; 20 21 void doit(int root,int res) 22 { 23 if (res<=0) return; 24 for (int i = 1;i <= g[root][0];i++) 25 { 26 for (int j = 0;j <= res-1;j++) f[g[root][i]][j] = f[root][j]+data[g[root][i]]; //先把i子樹已經算過的最優值賦值給j子樹 27 doit(g[root][i],res-1); //遞歸計算j子樹 28 for (int j = 1;j <= res;j++) 29 f[root][j] = max(f[root][j],f[g[root][i]][j-1]); //用j子樹最優值來更新i子樹 30 } 31 } 32 33 int main() 34 { 35 scanf("%d%d",&n,&m); 36 for (int i = 1;i <= n;i++) 37 { 38 scanf("%d%d",&x,&data[i]); 39 g[x][0]++; 40 g[x][g[x][0]] = i; 41 } 42 memset(f,0,sizeof(f)); 43 doit(0,m); 44 printf("%d\n",f[0][m]); 45 }
題目三:技能樹
【問題描述】
玩過Diablo的人對技能樹必定是很熟悉的。一顆技能樹的每一個結點都是一項技能,要學會這項技能則須要耗費必定的技能點數。只有學會了某一項技能之後,才能繼續學習它的後繼技能。每項技能又有着不一樣的級別,級別越高效果越好,而技能的升級也是須要 耗費技能點數的。
有個玩家積攢了必定的技能點數,他想盡量地利用這些技能點數來達到最好的效果。所以他給全部的級別都打上了分,他認爲效果越好的分數也越高。如今他要你幫忙尋找一個分配技能點數的方案,使得分數總和最高。
【輸入格式】
第一行是一個整數n(1<=n<=20),表示全部不一樣技能的總數。接下來依次給出n個不一樣技能的詳細狀況。每一個技能描述包括5行,第一行是該技能的名稱,第2行是該技能在技能樹中父技能的名稱,爲空則表示該技能不須要任何的先修技能便能學習。第3行是一個整數L(1<=L<=20),表示這項技能所能擁有的最高級別。第4行共有L個整數,其中第I個整數表示從地I-1級升到第I級所須要的技能點數(0級表示沒有學習過)。第5行包括L個整數,其中第I個整數表示從第I-1級升級到第I級的效果評分,分數不超過20。在技能描述以後,共有兩行,第1行是一個整數P,表示目前所擁有的技能點數。接下來1行是N個整數,依次表示角色當前習得的技能級別,0表示還未學習。這裏不會出現非法狀況。
【輸出格式】
S,表示最佳分配方案所得的分數總和。
【輸入樣例】
3
Freezing Arrow
Ice Arrow
3
3 3 3
15 4 6
Ice Arrow
Cold Arrow
2
4 3
10 17
Cold Arrow
3
3 3 2
15 5 2
10
0 0 1
【輸出樣例】
42
分析:允許我先樂呵一下。由於這是我第一道獨立作出來的多叉樹形揹包的題目!(智商低就是容易知足。。)
這個題目根上一題的區別在於,每一個結點的v[i]和c[i]是能夠在必定範圍內變化的,也就是說,要枚舉不一樣的v[j],而後下放到j子樹,而後加上不一樣的c[j]進行比較。
每一個結點有初始等級,等級爲0的不能做爲根節點,必需要升級,那麼對於等級不爲0的兒子j,首先將i所得到的全部容量m所有賦值給兒子j,也就是說先不升級。而後再跟等級爲0的兒子結點同樣,從初始等級+1到最高等級進行枚舉,設兩個變量p(累加存儲所需技能點數)和q(累加存儲所得到的分數),進行賦值、遞歸、更新的三步計算。
而後我一開始WA了一次,爲何呢,我想了一下才明白,枚舉每個等級,將i結點的最優值賦值給兒子j時,必須是任何升級前的初始值,否則每次用上一級的最優值來計算,會出現重複累加的狀況,最後答案會變大,因此枚舉到兒子結點j時,先用一個tmp數組存下F[i,0~m]的初始值,就是前面幾個兒子的最優值,而後對於不升級(初始等級不爲0)或者每一次升級,直接將tmp賦值給F[j,0~m-p]便可。
吼吼,再次慶祝一下第一次作出來的題目。話說如今寫題解感受理解又加深了一步。
參考代碼:
1 // 2 // 技能樹.cpp 3 // 樹形DP 4 // 5 // Created by TimmyXu on 13-8-3. 6 // Copyright (c) 2013年 TimmyXu. All rights reserved. 7 // 8 9 #include <iostream> 10 #include <algorithm> 11 #include <cstdio> 12 #include <cstring> 13 #include <string> 14 15 using namespace std; 16 17 const int maxn = 20+10; 18 19 string na[maxn],st; 20 21 int f[maxn][100+10],son[maxn][maxn],len[maxn],n,m,l1[maxn],data[maxn],l2[maxn][maxn],sc[maxn][maxn],y,top; 22 23 int findst(const string st) 24 { 25 for (int i = 1;i <= top;i++) 26 if (st == na[i]) 27 return i; 28 top++; 29 na[top] = st; 30 return top; 31 } 32 33 void doit(int root,int res) 34 { 35 int tmp[100+10]; 36 if (res < 0) return; 37 for (int i = 1;i <= son[root][0];i++) 38 { 39 for (int k = 0;k <= res;k++) 40 tmp[k] = f[root][k]; //先存儲初始最優值 41 if (l1[son[root][i]] != 0) //若是初始等級不爲0,那先嚐試不升級的最優 42 { 43 for (int k = 0;k <= res;k++) 44 f[son[root][i]][k] = f[root][k]; 45 doit(son[root][i],res); 46 for (int k = 0;k <= res;k++) 47 f[root][k] = max(f[root][k],f[son[root][i]][k]); 48 } 49 int p = 0,q = 0; 50 for (int j = l1[son[root][i]]+1;j <= len[son[root][i]];j++) //從當前等級升一級到最高等級進行枚舉 51 { 52 p += l2[son[root][i]][j]; //累加所需技能點 53 q += sc[son[root][i]][j]; //累加所得分數 54 for (int k = 0;k <= res-p;k++) 55 f[son[root][i]][k] = tmp[k]; 56 doit(son[root][i],res-p); 57 for (int k = p;k <= res;k++) 58 f[root][k] = max(f[root][k],f[son[root][i]][k-p]+q); 59 } 60 } 61 } 62 63 int main() 64 { 65 scanf("%d",&n); 66 top = 0; 67 memset(f,0,sizeof(f)); //dp數組 68 memset(son,0,sizeof(son)); //每一個結點的兒子結點 69 memset(len,0,sizeof(len)); //每一個結點的最高等級 70 memset(l1,0,sizeof(l1)); //每一個結點的初始等級 71 memset(data,0,sizeof(data)); //每一個節點在字符串序列中的編號 72 memset(l2,0,sizeof(l2)); //每一個節點升級所需技能點 73 memset(sc,0,sizeof(sc)); //每一個節點升級所得到加分 74 for (int i = 1;i <= n;i++) 75 { 76 getline(cin,st); 77 getline(cin,st); 78 data[i] = findst(st); 79 getline(cin,st); 80 if (st!="") y = findst(st); 81 else y = 0; 82 son[y][0]++; 83 son[y][son[y][0]] = data[i]; 84 scanf("%d",&len[data[i]]); 85 for (int j = 1;j <= len[data[i]];j++) 86 scanf("%d",&l2[data[i]][j]); 87 for (int j = 1;j <= len[data[i]];j++) 88 scanf("%d",&sc[data[i]][j]); 89 90 scanf("%d",&m); 91 for (int i = 1;i <= n;i++) 92 scanf("%d",&l1[data[i]]); 93 doit(0,m); 94 printf("%d\n",f[0][m]); 95 }
題目四:Ural 1018 二叉蘋果樹
【問題描述】
有一棵蘋果樹,若是樹枝有分叉,必定是分2叉(就是說沒有隻有1個兒子的結點)
這棵樹共有N個結點(葉子點或者樹枝分叉點),編號爲1-N,樹根編號必定是1。
咱們用一根樹枝兩端鏈接的結點的編號來描述一根樹枝的位置。下面是一顆有4個樹枝的樹
如今這顆樹枝條太多了,須要剪枝。可是一些樹枝上長有蘋果。
給定須要保留的樹枝數量,求出最多能留住多少蘋果。
【輸入格式】
第1行2個數,N和Q(1<=Q<= N,1<N<=100)。N表示樹的結點數,Q表示要保留的樹枝數量。接下來N-1行描述樹枝的信息。每行3個整數,前兩個是它鏈接的結點的編號。第3個數是這根樹枝上蘋果的數量。每根樹枝上的蘋果不超過30000個。
【輸出格式】
一個數,最多能留住的蘋果的數量。
【輸入樣例】Etree.in
5 2
1 3 1
1 4 10
2 3 20
3 5 20
【輸出樣例】Etree.out
21
分析:這種二叉樹的樹型DP仍是比較簡單的,首先建樹,找根,分以下幾種狀況討論:
參考代碼:
1 // 2 // 二叉蘋果樹.cpp 3 // 樹形DP 4 // 5 // Created by TimmyXu on 13-8-3. 6 // Copyright (c) 2013年 TimmyXu. All rights reserved. 7 // 8 9 #include <iostream> 10 #include <cstdio> 11 #include <cstring> 12 #include <string> 13 #include <algorithm> 14 15 using namespace std; 16 17 const int maxn = 100+10; 18 19 int n,q,g2[maxn][maxn],g[maxn][maxn],l[maxn],r[maxn],root,f[maxn][maxn],x,y,du[maxn]; 20 bool vis[maxn]; 21 22 void createtree(int x) 23 { 24 if (x == 0) return; 25 vis[x] = true; 26 for (int i = 1; i<= g2[x][0];i++) 27 if (!vis[g2[x][i]]) 28 { 29 if (l[x] == 0) l[x] = g2[x][i]; 30 else r[x] = g2[x][i]; 31 } 32 createtree(l[x]); 33 createtree(r[x]); 34 } 35 36 void doit(int root,int res) 37 { 38 if (res == 0) 39 { 40 f[root][res] = 0; 41 return; 42 } 43 if (l[root] == 0) 44 { 45 f[root][res] = 0; 46 return; 47 } 48 if (f[root][res]>0) return; //記憶化,若是算過則再也不計算 49 if (res == 1) 50 { 51 f[root][res] = max(g[root][l[root]],g[root][r[root]]); //若是隻有容量1,則取左右中最大值 52 return; 53 } 54 for (int i = 0;i <= res-2;i++) //若是容量大於1,則先假設左右都被分配到 55 { 56 doit(l[root],i); 57 doit(r[root],res-2-i); 58 f[root][res] = max(f[root][res],f[l[root]][i]+f[r[root]][res-i-2]); 59 } 60 61 f[root][res] += g[root][l[root]]+g[root][r[root]]; 62 63 doit(l[root],res-1); //只有左邊 64 f[root][res] = max(f[root][res],f[l[root]][res-1]+g[root][l[root]]); 65 66 doit(r[root],res-1); //只有右邊 67 f[root][res] = max(f[root][res],f[r[root]][res-1]+g[root][r[root]]); 68 69 return; 70 } 71 72 int main() 73 { 74 scanf("%d%d",&n,&q); 75 memset(g,0,sizeof(g)); 76 memset(g2,0,sizeof(g2)); 77 memset(f,0,sizeof(f)); 78 memset(l,0,sizeof(l)); 79 memset(r,0,sizeof(r)); 80 memset(vis,false,sizeof(vis)); 81 for (int i = 1;i < n;i++) 82 { 83 scanf("%d%d",&x,&y); 84 scanf("%d",&g[x][y]); 85 g[y][x] = g[x][y]; 86 g2[x][0]++; 87 g2[x][g2[x][0]] = y; 88 g2[y][0]++; 89 g2[y][g2[y][0]] = x; 90 } 91 root = 1; 92 for (int i = 1;i <= n;i++) 93 if (g2[i][0] == 2) 94 { 95 root = i; 96 break; 97 } 98 createtree(root); //建樹 99 doit(root,q); 100 printf("%d\n",f[root][q]); 101 }
題目五:將功補過
【問題背景】
做爲間諜專家的Elvis Han受竊取X星球軍事中心的祕密情報,他已經成功進入軍事中心。可是很不幸的是,在他尚未找到任務須要情報的時候就被發現,這時他清楚他不可能完成任務了,不過還有機會將功補過,也就是獲得一些不如任務情報有價值的其餘情報,若是獲得的情報的總價值大於等於任務情報價值,他也不會受到懲罰。很幸運的是他已經獲得的軍事中心的地圖,情報都是隱藏在各個道路上的,可是他只有時間遍歷必定數量的路(時間寶貴呀!還要逃跑。。)如今你作爲他的助手,給你地圖和每一個道路情報價值,但願你分析出,看他能不能將功補過。
【問題描述】
軍事中心是一個嚴格的二叉樹,也就是說,若是有個點能夠分道,必定是分出,也只分出2條道路,如今Elvis Han正處在第一個分道處,也就是說樹的根結點處。每條道路上都有一個分數,就是這個道路上的情報價值。可是他只有時間走M條路,他的最終情報價值總和就是他所通過的路的情報價值總和(假設他到過的路必定能夠把全部情報獲得)但願你給出一個方案使得他能夠儘可能多地獲取情報以便將功補過。
【輸入格式】
在輸入文件inform.in中,共有N行:
第一行:3個數據:N,M,Q(N表示有多少個路口,包括分道和不分道的路口;M表示他能夠有時間走的道路總數;Q表示他的任務情報的價值)
第2~N行:每行3個數據,Xi,Yi,Wi (X,Y表示第I條道路鏈接的2個路口,W表示這條道路上的情報價值分, 注意,全部數據均在Lonint範圍內)
【輸出格式】
在輸出文件inform.out中,共包含2行:
第一行:輸出TRUE/FALSE(注意大小寫),表示他是否能夠收集夠任務情報價值
第二行:輸出一個數據:
若是他能夠完成任務,就輸出他收集的情報總價值超過任務情報價值的部分。(正數)
若是不能完成任務,就輸出一個數,表示他不能還差多少分纔夠任務情報價值。(負數)
【輸入樣例1】
3 1 10
1 2 10
1 3 8
【輸出樣例1】
TRUE
0
樣例說明:(該部分沒必要輸出)
【輸入樣例2】
9 3 49
6 2 15
7 2 10
8 7 6
7 9 15
1 3 20
2 1 10
4 3 8
3 5 7
【輸出樣例2】
FALSE
-4
樣例說明:
【數據規模】
對於30%的數據 保證有N<=10
對於50%的數據 保證有N<=40
對於所有的數據 保證有 N<=100
分析:同上題
參考代碼:
1 // 2 // 將功補過.cpp 3 // 樹形DP 4 // 5 // Created by TimmyXu on 13-8-3. 6 // Copyright (c) 2013年 TimmyXu. All rights reserved. 7 // 8 9 #include <iostream> 10 #include <cstdio> 11 #include <cstring> 12 #include <string> 13 #include <algorithm> 14 15 using namespace std; 16 17 const int maxn = 100+10; 18 19 int f[maxn][maxn],n,m,q,x,y,w,g[maxn][maxn],g2[maxn][maxn],l[maxn],r[maxn],root; 20 bool vis[maxn]; 21 22 void createtree(int x) 23 { 24 if (x == 0) return; 25 vis[x] = true; 26 for (int i = 1;i <= g2[x][0];i++) 27 if (!vis[g2[x][i]]) 28 { 29 if (l[x] == 0) l[x] = g2[x][i]; 30 else r[x] = g2[x][i]; 31 } 32 createtree(l[x]); 33 createtree(r[x]); 34 } 35 36 void doit(int x,int res) 37 { 38 if (res == 0 || l[x] == 0) 39 { 40 f[x][res] = 0; 41 return; 42 } 43 if (f[x][res]>0) return; 44 if (res == 1) 45 { 46 f[x][res] = max(g[x][l[x]],g[x][r[x]]); 47 return; 48 } 49 for (int i = 0;i <= res-2;i++) 50 { 51 doit(l[x],i); 52 doit(r[x],res-i-2); 53 f[x][res] = max(f[x][res],f[l[x]][i]+f[r[x]][res-i-2]); 54 } 55 f[x][res] += g[x][l[x]]+g[x][r[x]]; 56 doit(l[x],res-1); 57 f[x][res] = max(f[x][res],f[l[x]][res-1]+g[x][l[x]]); 58 doit(r[x],res-1); 59 f[x][res] = max(f[x][res],f[r[x]][res-1]+g[x][r[x]]); 60 } 61 62 int main() 63 { 64 scanf("%d",&n,&m,&q); 65 memset(f,0,sizeof(f)); 66 memset(g,0,sizeof(g)); 67 memset(g2,0,sizeof(g2)); 68 memset(l,0,sizeof(l)); 69 memset(r,0,sizeof(r)); 70 memset(vis,false,sizeof(vis)); 71 for (int i = 1;i < n;i++) 72 { 73 scanf("%d%d%d",&x,&y,&w); 74 g[x][y] = g[y][x] = w; 75 g2[x][0]++; 76 g2[x][g2[x][0]] = y; 77 g2[y][0]++; 78 g2[y][g2[y][0]] = x; 79 } 80 for (int i = 1;i <= n;i++) 81 if (g2[i][0] == 2) 82 { 83 root = i; 84 break; 85 } 86 createtree(root); 87 doit(root,m); 88 if (f[root][m] >= q) 89 printf("TRUE\n%d\n",f[root][m]-q); 90 else printf("FALSE\n%d\n",f[root][m]-q); 91 }
題目六:加分二叉樹
【問題描述】
設一個n個節點的二叉樹tree的中序遍歷爲(l,2,3,…,n),其中數字1,2,3,…,n爲節點編號。每一個節點都有一個分數(均爲正整數),記第j個節點的分數爲di,tree及它的每一個子樹都有一個加分,任一棵子樹subtree(也包含tree自己)的加分計算方法以下:
subtree的左子樹的加分× subtree的右子樹的加分+subtree的根的分數
若某個子樹爲空,規定其加分爲1,葉子的加分就是葉節點自己的分數。不考慮它的空
子樹。試求一棵符合中序遍歷爲(1,2,3,…,n)且加分最高的二叉樹tree。要求輸出;
(1)tree的最高加分
(2)tree的前序遍歷
【輸入格式】
第1行:一個整數n(n<30),爲節點個數。
第2行:n個用空格隔開的整數,爲每一個節點的分數(分數<100)。
【輸出格式】
第1行:一個整數,爲最高加分(結果不會超過4,000,000,000)。
第2行:n個用空格隔開的整數,爲該樹的前序遍歷。
【輸入樣例】
5
5 7 1 2 10
【輸出樣例】
145
3 1 2 4 5
分析:其實這個是一個區間動態規劃,不能算是一個嚴格的樹形DP。對於[l,r]範圍內的最大值,每次枚舉中間點做爲根,遞歸計算左子樹和右子樹,算出來的值跟最大值比較便可。
參考代碼:
1 // 2 // 加分二叉樹.cpp 3 // 樹形DP 4 // 5 // Created by TimmyXu on 13-8-3. 6 // Copyright (c) 2013年 TimmyXu. All rights reserved. 7 // 8 9 #include <iostream> 10 #include <string> 11 #include <cstdio> 12 #include <cstring> 13 #include <algorithm> 14 15 using namespace std; 16 17 const int maxn = 30+10; 18 19 long long f[maxn][maxn]; 20 int g[maxn][maxn],n,data[maxn]; 21 22 void doit(int l,int r) 23 { 24 if (f[l][r] > 0) return; 25 if (l == r) 26 { 27 f[l][r] = data[l]; 28 g[l][r] = l; 29 return; 30 } 31 if (l > r) 32 { 33 f[l][r] = 1; 34 return; 35 } 36 for (int i = l;i <= r;i++) 37 { 38 doit(l,i-1); 39 doit(i+1,r); 40 if (f[l][i-1]*f[i+1][r]+data[i] > f[l][r]) 41 { 42 f[l][r] = f[l][i-1]*f[i+1][r]+data[i]; 43 g[l][r] = i; 44 } 45 } 46 } 47 48 void show(int l,int r) 49 { 50 if (l > r) return; 51 if (l == r) 52 { 53 printf("%d ",l); 54 return; 55 } 56 printf("%d ",g[l][r]); 57 show(l,g[l][r]-1); 58 show(g[l][r]+1,r); 59 } 60 61 int main() 62 { 63 scanf("%d",&n); 64 for (int i = 1;i <= n;i++) 65 scanf("%d",&data[i]); 66 memset(f,0,sizeof(f)); 67 memset(g,0,sizeof(g)); 68 doit(1,n); 69 printf("%lld\n",f[1][n]); 70 show(1,n); 71 printf("\n"); 72 }
題目七:Ural 1039 沒有上司的晚會
【背景】
有個公司要舉行一場晚會。爲了能玩得開心,公司領導決定:若是邀請了某我的,那麼必定不會邀請他的上司(上司的上司,上司的上司的上司……均可以邀請)。
【問題描述】
每一個參加晚會的人都能爲晚會增添一些氣氛,求一個邀請方案,使氣氛值的和最大。
【輸入格式】
第1行一個整數N(1<=N<=6000)表示公司的人數。
接下來N行每行一個整數。第i行的數表示第i我的的氣氛值x(-128<=x<=127)。
接下來每行兩個整數L,K。表示第K我的是第L我的的上司。
輸入以0 0結束。
【輸出格式】
一個數,最大的氣氛值和。
【輸入樣例】
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0
【輸出樣例】
5
分析:根據題目給出的矛盾關係寫出動規方程:
F[i,0]表示不邀請i,以i爲根的子樹所得到的最大值
F[i,1]表示邀請i,以i爲根的子樹所得到的最大值
轉移以下:
參考代碼:
1 // 2 // 沒有上司的晚會.cpp 3 // 樹形DP 4 // 5 // Created by TimmyXu on 13-8-3. 6 // Copyright (c) 2013年 TimmyXu. All rights reserved. 7 // 8 9 #include <iostream> 10 #include <algorithm> 11 #include <cstdio> 12 #include <cstring> 13 #include <string> 14 15 using namespace std; 16 17 const int maxn = 6000+10; 18 19 int n,root,du[maxn],data[maxn],f[maxn][2],g[maxn][maxn],x,y; 20 21 void doit(int x) 22 { 23 f[x][0] = 0; 24 f[x][1] = data[x]; 25 for (int i = 1;i <= g[x][0];i++) 26 { 27 doit(g[x][i]); 28 f[x][0] += max(f[g[x][i]][0],f[g[x][i]][1]); 29 f[x][1] += f[g[x][i]][0]; 30 } 31 return; 32 } 33 34 int main() 35 { 36 scanf("%d",&n); 37 for (int i = 1;i <= n;i++) 38 scanf("%d",&data[i]); 39 memset(f,0,sizeof(f)); 40 memset(g,0,sizeof(g)); 41 memset(du,0,sizeof(du)); 42 for (int i = 1;i < n;i++) 43 { 44 scanf("%d%d",&x,&y); 45 du[x]++; 46 g[y][0]++; 47 g[y][g[y][0]]= x; 48 } 49 for (int i = 1;i <= n;i++) 50 if (du[i] == 0) 51 { 52 root = i; 53 break; 54 } 55 doit(root); 56 printf("%d\n",max(f[root][0],f[root][1])); 57 }
總結:樹形dp的題目,若是單純給出節點間矛盾關係,或者在二叉樹上作揹包,都是比較簡單的。我目前遇到的題目中,多叉樹揹包是一個難點,但願本身能繼續學習,能拿下。OK,下午二分圖最大流搞起!