DP专题·四(树形dp)

1、poj 115 TELE

  题意:一个树型网络上有n个结点,1~n-m为信号传送器,n-m+1~n为观众,当信号传送给观众后,观众会付费观看,每铺设一条道路需要一定费用。现在求以1为根,使得收到观众的费用-铺设道路的费用>=0的情况下,能最多给多少个观众观看?

  思路:树形dp,dp[i][j]表示以i为根的子树中选择j个观众(叶子)最大的收益。

  ①如果当前结点为叶子结点,那么其dp[i][0]=0,dp[i][1]=val[i].

  ②如果为其他结点,则dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[son][k]-cost)(i:max->0;j:0->max)(表示当前结点选j个叶子,其中k个叶子来自son这棵子树).每个结点的容量为以其为根的子树的所有叶结点数目,可以通过dp回溯累加。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 using namespace std;
 6 const int maxn = 3010;
 7 const int maxe = 6020;
 8 const int INF = 0x3f3f3f;
 9 int wson[maxn];//以i为根的子树的容量(可选的叶子数目)
10 int val[maxn];
11 struct edge
12 {
13     int from, to, cost, next;
14     edge(int ff=0,int tt=0,int cc=0,int nn=0):from(ff),to(tt),cost(cc),next(nn){ }
15 }Edge[maxe];
16 int Head[maxn],totedge;
17 int n, m;//1为根,2~n-m为传送器,n-m+1~n为观众(叶子)
18 int dp[maxn][maxn];//dp[i][j]表示以i为子树选择j个叶子的最大v(叶子值-路值)
19 bool vis[maxn];//表示该点是否已经经过
20 
21 void Init()
22 {
23     memset(Edge, 0, sizeof(Edge));
24     memset(Head, -1, sizeof(Head));
25     memset(dp, -INF, sizeof(dp));
26     memset(vis, 0, sizeof(vis));
27     totedge = 0;
28 }
29 
30 void DP(int now, int pre)
31 {
32     vis[now] = true;
33     if (now >= n - m + 1)
34     {//如果为叶子
35         dp[now][0]=0,dp[now][1] = val[now], wson[now] = 1;
36         return;
37     }
38     else dp[now][0] = 0;
39     for (int e = Head[now]; e != -1; e = Edge[e].next)
40     {
41         int u = Edge[e].to, w = Edge[e].cost;
42         if (vis[u]) continue;
43         DP(u, now);
44         wson[now] += wson[u];
45         for (int j = wson[now]; j >= 1; j--)
46         {//枚举当前结点选择叶子个数(从大往小,01背包)
47             for (int k = 1; k <= min(wson[u],j); k++)
48             {//枚举子节点的叶子个数
49                 dp[now][j] = max(dp[now][j], dp[now][j - k] + dp[u][k] - w);
50             }
51         }
52     }
53 }
54 
55 void AddEdge(int from, int to, int cost)
56 {
57     Edge[++totedge] = edge(from, to, cost,Head[from]);
58     Head[from] = totedge;
59     Edge[++totedge] = edge(to, from, cost, Head[to]);
60     Head[to] = totedge;
61 }
62 
63 int main()
64 {
65     while (~scanf("%d%d", &n, &m))
66     {
67         Init();
68         for (int i = 1; i <= n - m; i++)
69         {
70             int k;
71             scanf("%d", &k);
72             for (int j = 1; j <= k; j++)
73             {
74                 int to, v;
75                 scanf("%d%d", &to, &v);
76                 AddEdge(i, to, v);
77             }
78         }
79         for (int i = n - m + 1; i <= n; i++) scanf("%d", val + i);
80         DP(1, 0);
81         for (int i = m; i >= 0; i--)
82         {
83             if (dp[1][i] >= 0)
84             {
85                 printf("%d\n",i);
86                 break;
87             }
88         }
89     }
90     return 0;
91 }
View Code

 2、poj 2486 Apple Tree

  题意:有棵树,每个结点上有权值。从结点1出发走K步,求能走到的点的权值之和最大。

  思路:dp[i][j][0]表示从结点i走j步不会到i时的最大值,dp[i][j][1]表示从结点i走j步回到结点i时最大值。

①回到结点u:从u出发,要回到u,需要多走两步u-v,v-u,分配给v子树k步,其他子树j-k步,都返回:
    dp[u][j][1]=max(dp[u][j][1],dp[u][j-k-2][1]+dp[v][k][1])
②不回到结点u:

  先从其他子树回到u,再在子树v走k步,不回到u:
    dp[u][j][0]=max(dp[u][j][0],dp[u][j-k-1][1]+dp[v][k][0])
  或者先走k步遍历子树v回到u,在从u走j-k-2步到其他子树,不回到u
    dp[u][j][0]=max(dp[u][j][0],dp[v][k][1]+dp[u][j-k-2][0])

参考:http://www.cnblogs.com/wuyiqi/archive/2012/01/09/2316758.html

 

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<cstdio>
 5 using namespace std;
 6 const int maxn = 110;
 7 const int maxk = 210;
 8 const int INF = 0x3f3f3f3f;
 9 int dp[maxn][maxk][2];//dp[i][j][0]表示从结点i走j步不会到i时的最大值,dp[i][j][1]表示从结点i走j步回到结点i时最大值。
10 //回到结点u:从u出发,要回到u,需要多走两步u-v,v-u,分配给v子树k步,其他子树j-k步,都返回:
11 //dp[u][j][1]=max(dp[u][j][1],dp[u][j-k-2][1]+dp[v][k][1])
12 //不回到结点u:先从其他子树回到u,再在子树v走k步,不回到u
13 //dp[u][j][0]=max(dp[u][j][0],dp[u][j-k-1][1]+dp[v][k][0])
14 //或者先走k步遍历子树v回到u,在从u走j-k-2步到其他子树,不回到u
15 //dp[u][j][0]=max(dp[u][j][0],dp[v][k][1]+dp[u][j-k-2][0])
16 int Head[maxn],totedge;
17 struct edge
18 {
19     int from, to, next;
20     edge(int ff=0,int tt=0,int nn=0):from(ff),to(tt),next(nn){ }
21 }Edge[2*maxn];
22 
23 int N,K;
24 bool vis[maxn];
25 int val[maxn];
26 
27 void AddEdge(int from, int to)
28 {
29     Edge[++totedge] = edge(from, to, Head[from]);
30     Head[from] = totedge;
31     Edge[++totedge] = edge(to, from, Head[to]);
32     Head[to] = totedge;
33 }
34 
35 void Init()
36 {
37     memset(dp, 0, sizeof(dp));
38     memset(Head, -1, sizeof(Head));
39     memset(vis, 0, sizeof(vis));
40     totedge = 0;
41 }
42 
43 void DP(int now)
44 {
45     vis[now] = true;
46     for (int u = Head[now]; u != -1; u = Edge[u].next)
47     {
48         int v = Edge[u].to;
49         if (vis[v]) continue;
50         DP(v);
51         for (int j = K; j >= 0; j--)
52         {
53             for (int k = 0; k <= j; k++)
54             {
55                 if (j - k - 2 >= 0)
56                 {
57                     dp[now][j][1] = max(dp[now][j][1], dp[now][j - k - 2][1] + dp[v][k][1]);
58                     dp[now][j][0] = max(dp[now][j][0], dp[v][k][1] + dp[now][j - k - 2][0]);
59                 }
60                 if (j - k - 1 >= 0) dp[now][j][0] = max(dp[now][j][0], dp[now][j - k - 1][1] + dp[v][k][0]);
61             }
62         }
63 
64     }
65 }
66 int main()
67 {
68     while (~scanf("%d%d", &N, &K))
69     {
70         Init();
71         for (int i = 1; i <= N; i++)
72         {
73             scanf("%d", val+i);
74             //dp[i][0][0] = dp[i][0][1] = val[i];
75             for (int j = 0; j <= K; j++) dp[i][j][0] = dp[i][j][1] = val[i];
76         }
77         for (int i = 1; i < N; i++)
78         {
79             int u, v;
80             scanf("%d%d", &u, &v);
81             AddEdge(u, v);
82         }
83         DP(1);
84         //int ans = 0;
85         //for (int i = 0; i <= K; i++)
86         //{
87         //    ans = max(ans, max(dp[1][i][0], dp[1][i][1]));
88         //}
89         printf("%d\n", max(dp[1][K][0],dp[1][K][1]));
90     }
91     return 0;
92 }
View Code

 3、poj 1947 Rebuilding Roads  

  题意:需要把一棵树拆出一个P结点的新子树,问需要的删除的最少边数为多少?

  思路:dp[i][j]表示以i为根的子树形成j个结点的新树需要删去的最少边数。刚开始时,dp[i][1]=以i为根的子树的子树个数(即i的孩子的个数),即当做删除所有孩子。然后,dp[now][mv] = min(dp[now][mv], dp[now][mv - k]-1 + dp[u][k]),表示当考虑其u孩子形成的子树时,需要dp[now][mv - k]-1,因为之前所有dp操作都没有涉及当前子树(即表示删除该子树),现在考虑该子树时需要把连接子树和父结点的边添加回去,即删除的边数-1。

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstdio>
 4 #include<cstring>
 5 using namespace std;
 6 
 7 int n, p;
 8 const int maxn = 160;
 9 const int INF = 0x3f3f3f3f;
10 struct edge
11 {
12     int from, to, next;
13     edge(int ff=0,int tt=0,int nn=0):from(ff),to(tt),next(nn){ }
14 }Edge[maxn*2];
15 int Head[maxn], totedge;
16 int dp[maxn][maxn];//dp[i][j]表示以i为根的子树形成j个结点的新树需要删去的最少边数
17 bool vis[maxn];
18 
19 void Init()
20 {
21     memset(dp,INF, sizeof(dp));
22     for (int i = 1; i <= n; i++) dp[i][1] = 0;
23     memset(Head, -1, sizeof(Head));
24     memset(Edge,0, sizeof(Edge));
25     memset(vis, 0, sizeof(vis));
26     totedge = 0;
27 }
28 
29 void addEdge(int from, int to)
30 {
31     Edge[++totedge] = edge(from, to, Head[from]);
32     Head[from] = totedge;
33     Edge[++totedge] = edge(to, from, Head[to]);
34     Head[to] = totedge;
35 }
36 
37 void DP(int now)
38 {
39     vis[now] = true;
40 
41     for (int i = Head[now]; i != -1; i = Edge[i].next)
42     {
43         int u = Edge[i].to;
44         if (vis[u]) continue;
45         DP(u);
46         for (int mv = p; mv >= 1; mv--)
47         {
48             for (int k = 1; k < mv; k++)
49             {//枚举加该子树的结点数
50                 dp[now][mv] = min(dp[now][mv], dp[now][mv - k]-1 + dp[u][k]);//因为之前并没有添加该子树,所以删除的边数-1,表示添加该子树
51             }
52         }
53     }
54 }
55 int main()
56 {
57     while (~scanf("%d%d", &n, &p))
58     {
59         Init();
60         for (int i = 1; i < n; i++)
61         {
62             int from, to;
63             scanf("%d%d", &from, &to);
64             addEdge(from, to);
65             dp[from][1]++;
66         }
67         DP(1);//假设1为根结点
68         int ans = INF;
69         for (int i = 1; i <= n; i++)
70         {
71             if(i==1)ans = min(ans, dp[i][p]);
72             else ans = min(ans, dp[i][p] + 1);//如果非根节点,因为还要删去和父亲所连接的一条边,+1
73         }
74         printf("%d\n", ans);
75     }
76     return 0;
77 }
View Code

 

posted @ 2017-11-01 19:09  萌萌的美男子  阅读(245)  评论(0编辑  收藏  举报