三道简单树型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+背包,如果不复杂的化,也有点按部就班的感觉了~下次想出状态,寻轨道距应该就能做出来了。

你可能感兴趣的:(poj)