【ACM/ICPC2013】樹形動態規劃專題

前言:按照計劃,昨天應該是完成樹形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]表示i點不放,且以i爲根節點的子樹(包括i節點)所有被觀察到;
    F[i,1]表示i點不放,且以i爲根節點的子樹(能夠不包括i節點)所有被觀察到;
    F[i,2]表示i點放,且以i爲根節點的子樹所有被觀察到;

轉移以下
一、由F[i,0]定義可知,設j爲i的兒子節點,至少要有一個i的兒子節點是放置守衛的,其他的兒子節點可放可不放,但因爲根節點i不放,因此其他的兒子節點若是不放的話,必須保證能被觀察到,即F[j][0];因此咱們須要枚舉必須放置的兒子節點,下面的轉移方程描述的很清楚:設計

    F[i,0] = min{Sigma(min(F[j][0],F[j,2]))+F[k,2]},其中k爲枚舉的必放的兒子節點,j爲除了k以外的兒子節點

二、由F[i,1]定義可知,i能夠被觀察到也能夠不被觀察到,但兒子節點必須都要被觀察到,轉移以下:3d

    F[i,1] = Sigma(min(F[j,0],F[j,2])) j是i的兒子節點

三、由F[i,2]定義可知,i點放置了守衛,因此對於每一個兒子節點都能被觀察到,取F[j,0],F[j,1],F[j,2]最小值便可:

    F[i,2] = min(F[j,0],F[j,1],F[j,2]) j是i的兒子節點
    對於葉節點i,F[i,0] = F[i,2] = data[i],F[i,1] = 0;

看了題解我是恍然大悟啊,智商不夠,這個轉移方程仍是比較難想的。

參考代碼

 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 }
View Code

題目二:選課(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 }
View Code

題目三:技能樹
【問題描述】
玩過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 }
View Code

題目四: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仍是比較簡單的,首先建樹,找根,分以下幾種狀況討論:

    一、固然容量爲0或爲葉結點,則返回
    二、當前容量爲1,則返回到左兒子邊權值和到右兒子邊權值中的最大值
    三、當前容量大於1,分三種狀況:1)所有給左兒子,2)所有給右兒子,3)左右兒子至少要給一個,剩下的枚舉分配求最大值。在三種狀況中取一個最大值便可。

參考代碼

  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 }
View Code

題目五:將功補過
【問題背景】
做爲間諜專家的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 }
View Code

題目六:加分二叉樹
【問題描述】
設一個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] > 0return;
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 }
View Code

題目七: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爲根的子樹所得到的最大值
轉移以下:

    F[i,0] = Sigma(max(F[j,0],F[j,1])) j是i的兒子 //不邀請i,那麼兒子可來可不來,取最大值
    F[i,1] = Sigma(F[j,0]) j是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 }
View Code

總結:樹形dp的題目,若是單純給出節點間矛盾關係,或者在二叉樹上作揹包,都是比較簡單的。我目前遇到的題目中,多叉樹揹包是一個難點,但願本身能繼續學習,能拿下。OK,下午二分圖最大流搞起!

相關文章
相關標籤/搜索