三道简单树型dp+01背包~~hdu1561,poj1947,zoj3626

以前学树型dp就是随便的看了几道题,没有特别注意树型dp中的小分类的总结,直到上次浙大月赛一道很简单的树型dp都不会,才意识到自己太水了~~come on!

hdu1561:题目给出了很多棵有根树,如果把每棵树的根节点都与0相连,则就是一棵完整的有根树了(N<=200),ACboy从根节点出发,他最多可以攻占m个城市,而每个城市的财富值不一定相同,问ACboy最多可以获得多少财富。就是以每个跟节点做01背包就可以了,dp[i][j]表示以i为根节点已经攻占了j个城市获得的最大财富。不过要注意一下最后要加上根节点的值,因为要求攻占了根节点才能往下攻占,当然节点0除外,具体见代码和注释:

View Code
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<cstring>
 5 #include<algorithm>
 6 #include<vector>
 7 #define see(x) cout<<#x<<":"<<x<<endl;
 8 using namespace std;
 9 const int maxn = 205;
10 int w[maxn], dp[maxn][maxn];
11 vector<int> son[maxn];
12 int n, m;
13 void dfs(int x){
14     int i, j, k;
15     if(son[x].size()==0){
16         dp[x][1] = w[x];
17         return;
18     }
19     for(i=0;i<son[x].size();i++){
20         dfs(son[x][i]);
21     }
22     for(i=0;i<son[x].size();i++){
23         for(j=m;j>=0;j--){
24             for(k=0;k<=j;k++){
25                 if(dp[son[x][i]][k]!=-1&&dp[x][j-k]!=-1){
26                     dp[x][j] = max(dp[x][j],dp[son[x][i]][k]+dp[x][j-k]);
27                 }
28             }
29         }
30     }
31 /*上面就是基本树型DP加背包的基本解法了*/
32     if(x!=0){  //x为0节点时,不需要这种要求
33         for(j=m;j>=0;j--){//处理一下必须攻占根节点才能攻占剩下的子节点的条件,逆序正好可以处理完
34             if(dp[x][j-1]!=-1){
35                 dp[x][j] = dp[x][j-1]+w[x];
36             }
37         }
38     }
39 }
40 void Init(int n){
41     memset(dp,-1,sizeof(dp));
42     for(int i=0;i<=n;i++){
43         dp[i][0] = 0;
44     }
45     for(int i=0;i<=n;i++){
46         son[i].clear();
47     }
48 }
49 int main(){
50     int i, j, k, l;
51     while(~scanf("%d%d",&n,&m)&&(n||m)){
52         Init(n);
53         for(i=1;i<=n;i++){
54             scanf("%d%d",&k,&w[i]);
55             son[k].push_back(i);
56         }
57         w[0] = 0; dfs(0);
58         printf("%d\n",dp[0][m]);
59     }
60 }

poj1947:给出一棵有根树,问孤立出大小为P的子树要断开多少边。就是一个容量为p的背包,树型dp。dp[i][j]表示以i为根孤立出j个节点的树至少需要断开多少条边,于是dp[x][j] = min{dp[x1][j1]+dp[x2][j2]+dp[x3][j3]+……+dp[xn][jn]},j1+j2+j3+……+jn = j,但是也不是完全这样了,其实在对每个背包进行更新时,应该是dp[x][j] = min{dp[x][j],dp[son[x][i]][k]+dp[x][j-k]-2},减2是因为要减去算了两次的x到son[x][i]的那条边,具体见代码,写的挺纠结的。

View Code
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cmath>
 5 #include<cstring>
 6 #include<vector>
 7 using namespace std;
 8 const int maxn = 155;
 9 vector<int> son[maxn];
10 bool vis[maxn];
11 int dp[maxn][maxn];
12 int n, p;
13 void dfs(int root, int x){
14     int i, j, k;
15     if(x==root) dp[x][1] = son[x].size();
16     else dp[x][1] = son[x].size()+1;
17 
18     for(i=0;i<son[x].size();i++){
19         dfs(root,son[x][i]);
20     }
21     for(i=0;i<son[x].size();i++){
22         for(j=p;j>=0;j--){
23             for(k=0;k<=j;k++){
24                 if(dp[x][k]<maxn&&dp[son[x][i]][j-k]<maxn){
25                     dp[x][j] = min(dp[x][j],dp[x][k]+dp[son[x][i]][j-k]-2);
26                 }
27             }
28         }
29     }
30 }
31 void Init(int n){
32     int i, j;
33     for(i=0;i<=n;i++){
34         for(j=0;j<=n;j++){
35             dp[i][j] = maxn;
36         }
37         son[i].clear();
38         vis[i] = 0;
39     }
40 }
41 int main(){
42     int i, j, k, x1, x2, ans;
43     while(~scanf("%d%d",&n,&p)){
44         Init(n);
45         for(i=0;i<n-1;i++){
46             scanf("%d%d",&x1,&x2);
47             son[x1].push_back(x2);
48             vis[x2] = 1;
49         }
50         for(i=1;i<=n;i++){
51             if(!vis[i]){
52                 dfs(i,i);break;
53             }
54         }
55         ans = maxn;
56         for(i=1;i<=n;i++){
57             ans = min(ans,dp[i][p]);
58         }
59         printf("%d\n",ans);
60     }
61 }

zoj3626:这个就是上次浙大月赛的题了,题意跟hdu1561类似,给出一棵树,n-1条边有不同的边全,n个城市有不同的财富,一个人最多只可以走m/2长度,为最多可以占领多少财富。dp[i][j]表示以i为根继续走了j个长度可以获得的最大财富,自己就是在刷完hdu1561之后简单的改改顺利的a了,两道题可能可以改成一样的写法,不过想到怎么写,就怎么写了

View Code
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<cstring>
 5 #include<algorithm>
 6 #include<vector>
 7 #define see(x) cout<<#x<<":"<<x<<endl;
 8 using namespace std;
 9 const int maxn = 110;
10 vector<int> son[maxn], w[maxn];
11 int val[maxn];
12 int dp[maxn][205];
13 int n, m;
14 void dfs(int x, int pre){
15     int i, j, k;
16     if(son[x].size()==1&&pre==son[x][0]){
17         return;
18     }
19     for(i=0;i<son[x].size();i++){
20         if(pre!=son[x][i]){
21             dfs(son[x][i],x);
22         }
23     }
24     for(i=0;i<son[x].size();i++){
25         if(pre!=son[x][i]){
26             for(j=m-w[x][i];j>=0;j--){
27                 for(k=0;k<=j;k++){
28                     if(dp[son[x][i]][k]!=-1&&dp[x][j-k]!=-1){
29                         dp[x][j+w[x][i]] = max(dp[x][j+w[x][i]],dp[son[x][i]][k]+dp[x][j-k]);
30                     }
31                 }
32             }
33         }
34     }
35 }
36 void Init(int n){
37     memset(dp,-1,sizeof(dp));
38     for(int i=0;i<=n;i++){
39         son[i].clear();
40         w[i].clear();
41     }
42 }
43 int main(){
44     int i, j, k, x1, x2, l, ans;
45     while(~scanf("%d",&n)){
46         Init(n);
47         for(i=1;i<=n;i++){
48             scanf("%d",&val[i]);
49             dp[i][0] = val[i];
50         }
51         for(i=0;i<n-1;i++){
52             scanf("%d%d%d",&x1,&x2,&l);
53             son[x1].push_back(x2);w[x1].push_back(l);
54             son[x2].push_back(x1);w[x2].push_back(l);
55         }
56         scanf("%d%d",&k,&m);
57         m/=2;dfs(k,-1);
58         ans = val[k];
59         for(i=0;i<=m;i++){
60             ans = max(ans,dp[k][i]);
61         }
62         printf("%d\n",ans);
63     }
64 }

 按理说dp都没啥模板,但是其实树上dp+背包,如果不复杂的化,也有点按部就班的感觉了~下次想出状态,寻轨道距应该就能做出来了。

原文地址:https://www.cnblogs.com/celia01/p/2619063.html