树形dp
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 }



浙公网安备 33010602011771号