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 }
来源:oschina
链接:https://my.oschina.net/u/4360442/blog/3231075