树形DP入门学习

这里是学习韦神的6道入门树形dp进行入门,本来应放在day12&&13里,但感觉这个应该单独放出来好点。

这里大部分题目都是参考的韦神的思想。

A - Anniversary party

题意:
一个树,每个点有一个“快乐”值,父子结点不能同时快乐,问这个结构的最大快乐值。

Thinking:

思考如何写出树规方程,即思考根与子节点的关系。

dp[i][0]:表示不邀请i员工其子树达到的最大快乐值,dp[i][1]则表示邀请。

这时根与子节点的关系就显然了。

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <vector>
 6 using namespace std;
 7 typedef long long LL;
 8 #define mst(s, t) memset(s, t, sizeof(s))
 9 const int INF = 0x3f3f3f3f;
10 const int maxn = 6010;
11 vector<int> G[maxn];
12 int father[maxn], dp[maxn][2];
13 void dfs(int root){
14     for(int i=0; i<G[root].size(); i++){
15         dfs(G[root][i]);
16     }
17     for(int i=0; i<G[root].size(); i++){
18         dp[root][0] += max(dp[G[root][i]][0], dp[G[root][i]][1]);
19         dp[root][1] += dp[G[root][i]][0];
20     }
21 }
22 int main() 
23 {
24     freopen("in.txt", "r", stdin);
25     mst(dp, 0); mst(father, -1);
26     int n;
27     scanf("%d", &n);
28     for(int i=1; i<=n; i++){
29         scanf("%d", &dp[i][1]);
30         G[i].clear();
31     }
32     int fa, so;
33     while(scanf("%d%d", &so, &fa) && fa && so){
34         G[fa].push_back(so);
35         father[so] = fa;
36     }
37     int root = 1;
38     while(father[root] != -1)    root=father[root];
39     dfs(root);
40     printf("%d\n", max(dp[root][0], dp[root][1]));
41     return 0;
42 }

B - Strategic game

题意:

现在要在一棵树上布置士兵,每个士兵在结点上,每个士兵可以守护其结点直接相连的全部边,问最少需要布置多少个士兵。

这题解法与上题相似。

1 dp[root][0] += dp[G[root][i]][1];
2 dp[root][1] += min(dp[G[root][i]][0], dp[G[root][i]][1]);

在读题时想了下结点A的父节点B的变化会影响到A和B的父节点C,会影响到总人数,后来又想了想,这不就是dp要解决的问题呀,在每个阶段做一个决策,以求达到预定的效果。

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <vector>
 6 using namespace std;
 7 typedef long long LL;
 8 #define mst(s, t) memset(s, t, sizeof(s))
 9 const int INF = 0x3f3f3f3f;
10 const int maxn = 1510;
11 int dp[maxn][2], father[maxn];
12 vector<int> G[maxn];
13 void dfs(int root){
14     for(int i=0; i<G[root].size(); i++){
15         dfs(G[root][i]);
16     }
17     for(int i=0; i<G[root].size(); i++){
18         dp[root][0] += dp[G[root][i]][1];
19         dp[root][1] += min(dp[G[root][i]][0], dp[G[root][i]][1]);
20     }
21 }
22 int main() 
23 {
24     //freopen("in.txt", "r", stdin);
25     int n;
26     while( scanf("%d", &n) != EOF){
27         for(int i=0; i<=n; i++){
28             G[i].clear();
29             dp[i][1] = 1,  dp[i][0] = 0;
30             father[i] = -1;
31         }
32         for(int i=0; i<n; i++){
33             int root, node, cnt;
34             scanf("%d:(%d)",&root, &cnt);
35             for(int i=0; i<cnt; i++){
36                 scanf("%d", &node);
37                 G[root].push_back(node);
38                 father[node] = root;
39             }
40         }
41         int root = 1;
42         while(father[root] != -1) root=father[root];
43         dfs(root);
44         printf("%d\n", min(dp[root][0], dp[root][1]));
45     }
46     return 0;
47 }
View Code

C - Tree Cutting 

题意:

一棵无向树,结点为n(<=10,000),删除哪些结点可以使得新图中每一棵树结点小于n/2。

Thinking:

真的是菜的无语,面对不会写的题总有懒于思考的毛病。

下面记录解决此题的心得:这题给我一种搜索而非dp的感觉,可能有什么我没发现的深意吧。

在遍历树的过程中,访问每个node,维护两个值:

  1. 所有子树的结点数的最大值childmax
  2. 所有子树(这里包括node)的结点数之和sum。

递归过程中用上一层的sum,不断更新这一层的childmax。

而childmax和sum则共同用来判断这个node是否可以删除。

下面再分析判断条件: childmax<=n/2 && n-sum<=n/2

childmax<=n/2 :去掉node后,原先node的子树均满足条件。

n-sum<=n/2  :去掉node后,原先除node和node的所有子树外的树(就当是node的祖先树吧)均满足条件。

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <vector>
 6 using namespace std;
 7 typedef long long LL;
 8 #define mst(s, t) memset(s, t, sizeof(s))
 9 const int INF = 0x3f3f3f3f;
10 const int maxn = 10010;
11 vector<int> G[maxn];
12 int ans[maxn], num, n;
13 int dfs(int node, int father){
14     int sum = 1, childmax = 0;   //若是叶子结点则return sum=1,否则求其子树(包括自己)的总结点数
15     for(int i=0; i<G[node].size(); i++){
16         if(G[node][i] == father)continue; //因为是树结构,这里可以在无向时避免遍历成环 
17         int sum_son = dfs(G[node][i], node);
18         childmax = max(sum_son, childmax);//所有子树的结点数的最大值
19         sum += sum_son;//sum:node的子树的结点数和
20     }
21     childmax = max(childmax, n-sum);
22     if(childmax <= n/2){
23         /*
24          * 当node结点的孩子结点的结点数最大为Sum,若Sum<=n/2,则该点符合条件
25          * 因为去掉node后,任意子树结点数<=n/2, max()保证其非子树结点和仍<=n/2
26          * 故该点满足条件
27         */
28         ans[num++] = node;
29     }
30     return sum;
31 }
32 int main() 
33 {
34     //freopen("in.txt", "r", stdin);
35     scanf("%d", &n);
36     for(int i=0; i<n-1; i++){
37         int a, b;
38         scanf("%d%d", &a, &b);
39         G[a].push_back(b);
40         G[b].push_back(a);
41     }
42     num = 0;
43     int tmp = dfs(1, 0);
44     //cout << n << "==" << tmp << endl; //验证
45     sort(ans, ans+num);
46     if(num){
47         for(int i=0; i<num; i++){
48             printf("%d\n", ans[i]);
49         }
50     }else{
51         printf("NONE\n");
52     }
53     return 0;
54 }

D - Tree of Tree

题意:一棵结点带权树,大小(结点数)为k的子树的权值和最大为多少。

这道题促使我写这篇学习心得,感觉稍微需要点思考的dp题我连思路都看得费劲。博客里的思路真的是想了好久,又找了份前辈的AC代码敲了敲(敲出来竟然连样例都没过,哎),趁热记录下自己的划水心得。

开始是想到要用状态dp[i]][j]表示node i的 结点数为j的子树 的最大权值和。 但是如何动态规划却没有思路。

这里对每个子节点进行背包dp, dp[j] = max(dp[j], dp[j-w[i]]+v[i]) ,从后往前dp是因为若从后往前会使v的某一个t被重复选取。

这道题整体思路还不清晰,要再多看看。

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <vector>
 6 using namespace std;
 7 typedef long long LL;
 8 #define mst(s, t) memset(s, t, sizeof(s))
 9 const int INF = 0x3f3f3f3f;
10 const int maxn = 110;
11 vector<int> G[maxn];
12 int dp[maxn][maxn];  //dp[i][j]:node[i]结点数为j的子树的最大权值
13 int k, ans, cnt[maxn], weight[maxn];
14 int dfs(int node, int father){
15     cnt[node] = 1;
16     for(int i=0; i<G[node].size(); i++){
17         if(G[node][i] == father) continue;
18         cnt[node] += dfs(G[node][i], node);
19     }
20     dp[node][1] = weight[node];  
21     //这里初始化不能在main()内  ?? 
22 /*
23  *  dp[node][j-t]是之前的子节点为根更新的子树产生的
24  *  dp[v][t]是以当前子节点为根的子树产生的
25  *  j如果顺序遍历,前面dp[node][j]的更新会影响后面的dp[node][j-t],导致后面
26  *更新dp[node][j]时是一当前子节点为根的子树产生的
27 */
28     for(int i = 0; i < G[node].size(); i++){
29         int v = G[node][i];
30         for(int j = cnt[node]; j >= 1; j--){
31             for(int t = 0; t<j && t<=cnt[v]; t++){
32                 dp[node][j] = max(dp[node][j], dp[node][j-t]+dp[v][t]);
33             }
34         }
35     }
36     ans = max(ans, dp[node][k]);
37     return cnt[node];
38 }
39 int main() 
40 {
41     freopen("in.txt", "r", stdin);
42     int n;
43     while(scanf("%d%d",&n, &k) != EOF){
44         mst(dp, 0);  ans = 0;
45         for(int i=0; i<maxn; i++){
46             G[i].clear();
47         }
48         for(int i=0; i<n; i++){
49             scanf("%d", &weight[i]);
50         }
51         int a, b;
52         for(int i = 1; i < n; i++){
53             scanf("%d%d", &a, &b);
54             G[a].push_back(b);
55             G[b].push_back(a);
56         }
57         dfs(0, -1);
58         printf("%d\n", ans);
59     }
60     return 0;
61 }

猜你喜欢

转载自www.cnblogs.com/seaupnice/p/9471700.html