树形dp

那年仲夏 提交于 2020-04-12 17:53:12

acwing 1072.树的最长路径

https://www.acwing.com/problem/content/1074/

如何在一颗有边权的树上求树的直径?方法是:任取树上一点,求出距离该点距离的最大值和次大值,相加即是路径, 遍历所有点,求出最长的路径。

如何在一颗没有边权的树上求树的直径?方法是:任取树上的一个点$a$, 找到离该点最远的一个点$u$,那么$u$一定是某一条直径的端点,从$u$开始找到离$u$最远的点$v$,那么$u-v$就是树的一条直径。将所有的直径取最大值,就是树的最长路径。(本题的做法是在有边权的树上求树的直径

核心在于为什么$u$一定是某一条直径的端点。

 

证明:

(1)当$au$和$bc$不相交时:假设$a$为树上的任意一点,$u$为离$a$最远的一点并且$u$不是某一直径的端点,$bc$为树上的一条直径。因为是一颗树,所以是连通的,所以$au$和$bc$中间有两个点相连。因为$u$是离$a$最远的一个点,所以①>=②+③,所以①+②>=③,所以存在一一条路径$bu$比当前的直径$bc$更长,矛盾。所以$u$一定是某一直径的端点。

(2)当$au$和$bc$相交时:因为$u$是离$a$最远的一点,所以④>=③,那么①+④>=①+③, 与$bc$是直径矛盾,所以$u$一定是某一直径的端点。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 
 5 using namespace std;
 6 
 7 const int N = 10010, M = 2 * N;
 8 int e[M], ne[M], h[M], w[M], idx;
 9 int ans;
10 
11 void add(int a, int b, int c)
12 {
13     e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
14 }
15 
16 int dfs(int u, int father)
17 {
18     int dist = 0; // 表示从当前点往下走的最大长度
19     int d1 = 0, d2 = 0;
20 
21     for (int i = h[u]; i != -1; i = ne[i])
22     {
23         int j = e[i];
24         if (j == father) continue;
25         int d = dfs(j, u) + w[i];
26         dist = max(dist, d);
27 
28         if (d >= d1) d2 = d1, d1 = d;
29         else if (d > d2) d2 = d;
30     }
31 
32     ans = max(ans, d1 + d2);
33 
34     return dist;
35 }
36 
37 
38 
39 int main(){
40     int n;
41     cin >> n;
42     memset(h, -1, sizeof h);
43     for(int i = 0 ; i < n - 1 ; i ++)
44     {
45         int a, b, c;
46         cin >> a >> b >> c;
47         add(a, b, c), add(b, a, c);
48     }
49     
50     dfs(1, -1);
51     
52     cout << ans << endl;
53     
54     return 0;
55 }

 

 

acwing 1073.树的中心

https://www.acwing.com/problem/content/1075/

对于一颗树,要求中心。需要求出每个点向下的最长路径和向上的最长路径。向下的最长路径只需用$dfs$预处理一遍就可以得出。但是对于向上的最长路径情况有些不同,例如对于$j$向上的最长路径来说,是要分三种情况的:

①$j$向上的最长路径是$u$向下的最长路径 + $s$。

②$j$向上的最长路径是$u$向下的次长路径 + $s$。

③$j$向上的最长路径是$u$向上的最长路径 + $s$。

对于①和②,情况是互斥的。当$u$向下的最长路径是经过j的,我们就只能使用$u$向下的次长路径,反之我们使用$u$向下的最长路径。

所以结论是在$dfs$时,需要用一个数组记录最长路径是由哪个节点转移。如果是$j$节点转移过来,就比较$u$向下的次长路径和$u$向上的最长路径的大小。如果不是$j$节点转移过来,就比较$u$向下的最长路径和$u$向上的最长路径大小。

还需要注意的是:叶子节点没有向下的路径,根节点没有向上的路径。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 
 5 using namespace std;
 6 
 7 const int N = 10010, INF = 0x3f3f3f3f;
 8 int d1[N], d2[N], up[N], p[N];
 9 int e[2 * N], ne[2 * N], h[N], w[2 * N], idx;
10 bool is_leaf[N];
11 int n;
12 
13 void add(int a, int b, int c)
14 {
15     e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
16 }
17 
18 int dfs_d(int u, int fa)
19 {
20     d1[u] = d2[u] = -INF;
21     for(int i = h[u] ; ~i ; i = ne[i])
22     {
23         int j = e[i];
24         if(j == fa)continue;
25         int d = dfs_d(j, u) + w[i];
26         if(d > d1[u])
27         {
28             d2[u] = d1[u];
29             d1[u] = d;
30             p[u] = j;//存储最长路径由哪个节点转移
31         }
32         else if(d > d2[u])d2[u] = d;
33     }
34     
35     if(d1[u] == -INF)
36     {
37         d1[u] = d2[u] = 0;
38         is_leaf[u] = true;
39     }
40     return d1[u];
41 }
42 
43 void dfs_u(int u, int fa)
44 {
45     for(int i = h[u] ; ~i ; i = ne[i])
46     {
47         int j = e[i];
48         if(j == fa)continue;
49         
50         if(p[u] == j)up[j] = max(up[u], d2[u]) + w[i];
51         else up[j] = max(up[u], d1[u]) + w[i];
52         
53         dfs_u(j, u);
54     }
55 }
56 
57 int main(){
58     cin >> n;
59     memset(h, -1, sizeof h);
60     for(int i = 0 ; i < n - 1 ; i ++)
61     {
62         int a, b, c;
63         cin >> a >> b >> c;
64         add(a, b, c), add(b, a, c);
65     }
66     
67     dfs_d(1, -1);//预处理每个点向下的最长路径
68     dfs_u(1, -1);//预处理每个点向上的最长路径
69     
70     int res = d1[1];
71     for(int i = 2 ; i <= n ; i ++)
72         if(is_leaf[i])res = min(res, up[i]);//叶子没有向下的路径
73         else res = min(res, max(up[i], d1[i]));
74         
75     cout << res << endl;
76     return 0;
77 }

 

acwing 1075.数字转换

https://www.acwing.com/problem/content/1077/

题意:一个数$x$和其约数之和(不包括自身)的$y$可以相互转移,求转移最多次数。就是求树的直径。

①如何建树:分析可知,每一个数的约数之和是不变的。所以每个数有且仅有一个约数之和,那么我们可以将每个数的约数之和$y$当做父节点,$x$当做子节点,这样就能建出树。

②如何处理约数之和:朴素的做法是遍历每一个数,求其约数相加,时间复杂度是$O(n\sqrt {n})$。不是很理想。事实上我们可以枚举一个数是哪些数的约数,这样能将时间复杂度降到$O(nlogn)$。

③可以知道,所有的合数最后一定会指向一个质数,而所有的质数最后都会指向$1$,所以建出来树是以$1$为根节点,只需从根节点开始$dfs$就可以。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 
 5 using namespace std;
 6 
 7 const int N = 50010;
 8 int n;
 9 int e[2 * N], ne[2 * N], h[N], idx;
10 int sum[N];
11 bool st[N];
12 int ans;
13 
14 void add(int a, int b)
15 {
16     e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
17 }
18 
19 int dfs(int u)//因为建出的数是单向向下的树,不会死循环
20 {
21     int d1 = 0, d2 = 0;
22     for(int i = h[u] ; ~i ; i = ne[i])
23     {
24         int j = e[i];
25         int d = dfs(j) + 1;
26         if(d > d1)d2 = d1, d1 = d;
27         else if(d > d2)d2 = d;
28         
29         ans = max(ans, d1 + d2);
30     }
31     return d1;
32 }
33 
34 int main(){
35     cin >> n;
36     for(int i = 1 ; i <= n ; i ++)
37         for(int j = 2 ; j <= n / i ; j ++)//因为约数不包括自身,所以j从2开始枚举
38             sum[i * j] += i;
39     
40     memset(h, -1, sizeof h);
41     for(int i = 2 ; i <= n ; i ++)//因为sum[1] = 0, 不能有0->1,题目要求
42         if(sum[i] < i)
43             add(sum[i], i);
44             
45     dfs(1);
46     
47     cout << ans << endl;
48     return 0;
49 }

 

 

acwing 285.没有上司的舞会

https://www.acwing.com/problem/content/287

题意:每条边上至多选择一个点,求最大权值。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 
 5 const int N = 6e3+10;
 6 using namespace std;
 7 int n ;
 8 int happy[N];
 9 int f[N][2];
10 int has_father[N];
11 int e[N],ne[N],idx,h[N];
12 
13 void add(int a,int b){
14     e[idx] = b;
15     ne[idx] = h[a];
16     h[a] = idx ++;
17 }
18 
19 void dfs(int u){
20     f[u][1] = happy[u];
21     
22     for(int i = h[u]; i != -1 ; i = ne[i]){
23         int j = e[i];
24         dfs(j);
25         
26         f[u][1] += f[j][0];
27         f[u][0] += max(f[j][1] , f[j][0]);
28     }
29 }
30 
31 
32 int main(){
33     cin>>n;
34     memset(h, -1, sizeof h);
35     for(int i = 1 ; i <= n ; i ++)//这里从1开始因为节点的标号是从1开始而不是从0开始
36         cin >> happy[i];
37     for(int i = 0 ; i < n - 1 ; i ++)
38     {
39         int a, b;
40         cin >> a >> b;
41         has_father[a] = 1;
42         add(b, a);
43     }
44     int root = 1;
45     while(has_father[root])root ++;
46     dfs(root);
47     cout << max(f[root][0], f[root][1]) << endl;
48     return 0;
49 }

 

 

acwing 323.战略游戏

https://www.acwing.com/problem/content/325/

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <string>
 5 
 6 using namespace std;
 7 
 8 const int N = 1510;
 9 int e[N * 2], ne[N * 2 ], h[N], idx;
10 int f[N][2], st[N];
11 int n;
12 int root;
13 
14 void add(int a, int b)
15 {
16     e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
17 }
18 
19 void dfs(int u)
20 {
21     f[u][1] = 1;
22     f[u][0] = 0;
23     for(int i = h[u] ; ~i ; i = ne[i])
24     {
25         int j = e[i];
26         dfs(j);
27         
28         f[u][1] += min(f[j][0], f[j][1]);
29         f[u][0] += f[j][1];
30     }
31 }
32 
33 int main(){
34     while(cin >> n)
35     {
36         memset(st, 0, sizeof st);
37         memset(h, -1, sizeof h);
38         idx = 0;
39         for(int i = 0 ; i < n ; i ++)
40         {
41             int id, cnt;
42             scanf("%d:(%d)", &id, &cnt);
43             while(cnt --)
44             {
45                 int x;
46                 cin >> x;
47                 add(id, x);
48                 st[x] = true;
49             }
50         }
51         
52         root = 0;
53         while(st[root])root ++;
54         
55         dfs(root);
56         
57         cout << min(f[root][1], f[root][0]) << endl;
58         
59     }
60     return 0;
61 }

 

 

acwing 1077.皇宫守卫

https://www.acwing.com/problem/content/1079/

 

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 
 5 using namespace std;
 6 
 7 const int N = 1510;
 8 int e[N], ne[N], h[N], idx;
 9 int w[N];
10 bool st[N];
11 int f[N][3];
12 int n;
13 
14 void add(int a, int b)
15 {
16     e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
17 }
18 
19 void dfs(int u)
20 {
21     f[u][2] = w[u];
22     f[u][0] = 0;
23     for(int i = h[u] ; ~i ; i = ne[i])
24     {
25         int j = e[i];
26         dfs(j);
27         
28         f[u][2] += min(min(f[j][0], f[j][1]), f[j][2]);
29         f[u][0] += min(f[j][1], f[j][2]);
30     }
31     
32     f[u][1] = 1e9;
33     for(int i = h[u] ; ~i ; i = ne[i])
34     {
35         int j = e[i];
36         
37         f[u][1] = min(f[u][1], f[j][2] + f[u][0] - min(f[j][1], f[j][2]));
38     }
39 }
40 
41 int main(){
42     cin >> n;
43     memset(h, -1, sizeof h);
44     for(int i = 1 ; i <= n ; i ++)
45     {
46         int id, cost, cnt;
47         cin >> id >> cost >> cnt;
48         w[id] = cost;
49         while(cnt --)
50         {
51             int x;
52             cin >> x;
53             add(id, x);
54             st[x] = true;
55         }
56     }
57     
58     int root = 1;
59     while(st[root])root ++;
60     
61     dfs(root);
62     
63     cout << min(f[root][1], f[root][2]) << endl;
64     return 0;
65 }

 

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!