學習大佬:樹的直徑求法及證明
樹的直徑
定義:
一棵樹的直徑就是這棵樹上存在的最長路徑。
給定一棵樹,樹中每條邊都有一個權值,樹中兩點之間的距離定義為連接兩點的路徑邊權之和。樹中最遠的兩個節點之間的距離被稱為樹的直徑,連接這兩點的路徑被稱為樹的最長鏈。后者通常也可稱為直徑,即直徑是一個數值概念,也可代指一條路徑。
求法:
一、樹形dp
時間復雜度:O( n );
優點:代碼量少實現方便。
不足:不容易記錄路徑。
實現過程:
狀態:d[ x ] 以當前結點 x 為根的 子樹的直徑。
我們枚舉每一個結點 x 以及 它要到達的下一個結點 Eiv。

這兩個結點所能達到的最大距離之和 加上 這兩個結點的邊權就有可能去更新樹的直徑。
即:樹的直徑 ans_max = max{ d[ x ] + d[ Eiv ] + edge[x, Eiv] } (1 <= x <= N)
那么 d[ x ] 通過什么更新呢?當然是由 它所連接下一個結點所能達到最大距離 來更新了;
即 d[ x ] = max{ d[ x ], d[ Eiv ] + edge[ x, Eiv ] };
核心代碼:
1 void dp(int st) 2 { 3 vis[st] = true; //當前結點已訪問 4 for(int i = head[st]; i != -1; i = edge[i].nxt){ 5 int Eiv = edge[i].v; 6 if(vis[Eiv]) continue; //不走回頭路 7 dp(Eiv); 8 ans_max = max(ans_max, d[st] + d[Eiv] + edge[i].w); //更新樹的直徑(由當前結點兩段之和更新) 9 d[st] = max(d[st], d[Eiv]+edge[i].w); //更新當前結點所能走的最長路徑(保留較長的那邊) 10 } 11 }
二、DFS || BFS
時間復雜度: O( n );
優點:可以在第一次 dfs/bfs記錄前驅
不足:代碼量稍大
實現過程:
前提:
兩次dfs或bfs。第一次任意選一個點進行dfs(bfs)找到離它最遠的點,此點就是最長路的一個端點,再以此點進行dfs(bfs),找到離它最遠的點,此點就是最長路的另一個端點,於是就找到了樹的直徑。
證明(by大佬):
假設此樹的最長路徑是從s到t,我們選擇的點為u。反證法:假設搜到的點是v。
1、v在這條最長路徑上,那么dis[u,v]>dis[u,v]+dis[v,s],顯然矛盾。
2、v不在這條最長路徑上,我們在最長路徑上選擇一個點為po,則dis[u,v]>dis[u,po]+dis[po,t],那么有dis[s,v]=dis[s,po]+dis[po,u]+dis[u,v]>dis[s,po]+dis[po,t]=dis[s,t],即dis[s,v]>dis[s,t],矛盾。
也許你想說u本身就在最長路徑,或則其它的一些情況,但其實都能用類似於上面的反證法來證明的。
綜上所述,你兩次dfs(bfs)就可以求出最長路徑的兩個端點和路徑長度。
核心代碼:
1 void dfs(int s) 2 { 3 for(int i = head[s]; i != -1; i = edge[i].nxt){ 4 int Eiv = edge[i].v; 5 if(fa[s] == Eiv) continue; //不走回頭路,也可以遞歸父親結點省去fa數組空間 6 fa[Eiv] = s; 7 dis[Eiv] = dis[s] + edge[i].w; //當前結點最長路徑 8 dfs(Eiv); 9 } 10 }
三、舉栗子
Cow Marathon
| Time Limit: 2000MS | Memory Limit: 30000K | |
| Total Submissions: 6925 | Accepted: 3279 | |
| Case Time Limit: 1000MS | ||
Description
Input
Output
Sample Input
7 6 1 6 13 E 6 3 9 E 3 5 7 S 4 1 3 N 2 4 20 W 4 7 2 S
Sample Output
52
Hint
題意概括:
建一顆 N 個節點 M 條邊的樹,求樹上兩點最長距離(樹的直徑);方向都是虛的,建雙向邊。
解題思路:
①樹形dp
AC code:
1 //樹形dp版 2 #include <cstdio> 3 #include <iostream> 4 #include <algorithm> 5 #include <cstring> 6 #include <vector> 7 #define INF 0x3f3f3f3f 8 using namespace std; 9 const int MAXN = 5e4+5; 10 int d[MAXN]; 11 bool vis[MAXN]; 12 int head[MAXN], cnt; 13 int N, M, ans_max; 14 struct Edge 15 { 16 int v, w, nxt; 17 Edge(int _v = 0, int _w = 0, int _nxt = 0):v(_v), w(_w), nxt(_nxt){}; 18 }edge[MAXN<<1]; 19 20 void init() 21 { 22 memset(head, -1, sizeof(head)); 23 memset(d, 0, sizeof(d)); 24 memset(vis, false, sizeof(vis)); 25 cnt = 0; 26 } 27 28 void AddEdge(int from, int to, int weight) 29 { 30 edge[cnt] = Edge(to, weight, head[from]); 31 head[from] = cnt++; 32 } 33 34 void dp(int st) 35 { 36 vis[st] = true; //當前結點已訪問 37 for(int i = head[st]; i != -1; i = edge[i].nxt){ 38 int Eiv = edge[i].v; 39 if(vis[Eiv]) continue; //不走回頭路 40 dp(Eiv); 41 ans_max = max(ans_max, d[st] + d[Eiv] + edge[i].w); //更新樹的直徑(由當前結點兩段之和更新) 42 d[st] = max(d[st], d[Eiv]+edge[i].w); //更新當前結點所能走的最長路徑(保留較長的那邊) 43 } 44 } 45 46 int main() 47 { 48 while(~scanf("%d%d", &N, &M)) 49 { 50 init(); 51 char ccc; 52 for(int i = 1, u, v, w; i <= M; i++){ 53 scanf("%d%d%d %c", &u, &v, &w, &ccc); 54 AddEdge(u, v, w); 55 AddEdge(v, u, w); 56 } 57 //printf("%d\n", ans); 58 ans_max = 0; 59 dp(1); 60 printf("%d\n", ans_max); 61 } 62 return 0; 63 }
②兩次DFS
AC code:
1 //DFS版 2 /* 3 #include <cstdio> 4 #include <iostream> 5 #include <algorithm> 6 #include <cstring> 7 #include <vector> 8 #define INF 0x3f3f3f3f 9 using namespace std; 10 const int MAXN = 5e4+5; 11 int dis[MAXN], fa[MAXN]; 12 int head[MAXN], cnt; 13 int N, M, ans; 14 15 struct Edge 16 { 17 int v, w, nxt; 18 Edge(int _v = 0, int _w = 0, int _nxt = 0):v(_v), w(_w), nxt(_nxt){}; 19 }edge[MAXN<<1]; 20 21 void AddEdge(int from, int to, int weight) 22 { 23 edge[cnt] = Edge(to, weight, head[from]); 24 head[from] = cnt++; 25 } 26 27 void init() 28 { 29 memset(head, -1, sizeof(head)); 30 for(int i = 0; i <= N; i++) fa[i] = i; 31 cnt = 0; 32 ans = 0; 33 } 34 35 void dfs(int s) 36 { 37 for(int i = head[s]; i != -1; i = edge[i].nxt){ 38 int Eiv = edge[i].v; 39 if(fa[s] == Eiv) continue; //不走回頭路,也可以遞歸父親結點省去fa數組空間 40 fa[Eiv] = s; 41 dis[Eiv] = dis[s] + edge[i].w; //當前結點最長路徑 42 dfs(Eiv); 43 } 44 } 45 46 int main() 47 { 48 while(~scanf("%d%d", &N, &M)) 49 { 50 init(); 51 char ccc; 52 for(int i = 1, u, v, w; i <= M; i++){ 53 scanf("%d%d%d %c", &u, &v, &w, &ccc); 54 AddEdge(u, v, w); 55 AddEdge(v, u, w); 56 } 57 //printf("%d\n", ans); 58 int ans_max = 0, ans_index = 0; 59 dfs(1); //第一次dfs找出樹的直徑所在的點 60 for(int i = 1; i <= N; i++){ 61 if(dis[i] > ans_max){ 62 ans_max = dis[i]; 63 ans_index = i; 64 } 65 dis[i] = 0; 66 fa[i] = i; 67 } 68 69 dfs(ans_index); //第二次dfs找出樹的直徑 70 for(int i = 1; i <= N; i++){ 71 if(dis[i] > ans_max) ans_max = dis[i]; 72 } 73 printf("%d\n", ans_max); 74 } 75 return 0; 76 }
Caterpillar
| Time Limit: 2000MS | Memory Limit: 65536K | |
| Total Submissions: 2505 | Accepted: 1180 |
Description
An undirected graph is called a caterpillar if it is connected, has no cycles, and there is a path in the graph where every node is either on this path or a neighbor of a node on the path. This path is called the spine of the caterpillar and the spine may not be unique. You are simply going to check graphs to see if they are caterpillars.
For example, the left graph below is not a caterpillar, but the right graph is. One possible spine is
shown by dots.
Input
There will be multiple test cases. Each test case starts with a line containing n indicating the number of nodes, numbered 1 through n (a value of n = 0 indicates end-of-input). The next line will contain an integer e indicating the number of edges. Starting on the following line will be e pairs n1 n2 indicating an undirected edge between nodes n1 and n1. This information may span multiple lines. You may assume that n ≤ 100 and e ≤ 300. Do not assume that the graphs in the test cases are connected or acyclic.
Output
For each test case generate one line of output. This line should either be
Graph g is a caterpillar.or
Graph g is not a caterpillar.
as appropriate, where g is the number of the graph, starting at 1.
Sample Input
22 21 1 2 2 3 2 4 2 5 2 6 6 7 6 10 10 8 9 10 10 12 11 12 12 13 12 17 18 17 15 17 15 14 16 15 17 20 20 21 20 22 20 19 16 15 1 2 2 3 5 2 4 2 2 6 6 7 6 8 6 9 9 10 10 12 10 11 10 14 10 13 13 16 13 15 0
Sample Output
Graph 1 is not a caterpillar. Graph 2 is a caterpillar.
題意概括:
首先需要判斷給的圖是不是一棵樹;
其次需要判斷是否存在一條路徑使得圖上所有的點要么在這條路徑上,要么距離該路徑的距離為 1。
解題思路:
判斷是否為一棵樹直接dfs判斷是否存在環即可;
為了讓最多的點滿足第二個條件,則這條路徑一定是樹的直徑。
兩次DFS求出樹的直徑,遍歷一遍判斷是否所有點都滿足條件。
為了做這個判斷,DFS時不僅要更新樹的直徑還要同時更新每一個結點能達到的最長路徑;
如果當前結點能達到的最長路徑等於樹的直徑說明該結點在樹的直徑上,如果不能則判斷他是否能通過相連的結點到達樹的直徑。
AC code:
1 #include <bits/stdc++.h> 2 #define INF 0x3f3f3f3f 3 using namespace std; 4 const int MAXN = 300; 5 struct Edge{int v; int nxt;}edge[MAXN<<1]; 6 int head[MAXN], cnt; 7 int dis[MAXN], bb[MAXN]; 8 bool vis[MAXN]; 9 int N, M; 10 int ans_max, ans_index; 11 bool chck; 12 13 void init() 14 { 15 memset(head, -1, sizeof(head)); 16 memset(vis, false, sizeof(vis)); 17 memset(dis, 0, sizeof(dis)); 18 memset(bb, 0, sizeof(bb)); 19 cnt = 0; 20 chck = false; 21 ans_max = 0; ans_index = 0; 22 } 23 24 void AddEdge(int from, int to) 25 { 26 edge[cnt].v = to; 27 edge[cnt]. nxt = head[from]; 28 head[from] = cnt++; 29 } 30 31 void check(int s, int fa) 32 { 33 vis[s] = true; 34 for(int i = head[s]; i != -1; i = edge[i].nxt){ 35 int Eiv = edge[i].v; 36 if(Eiv == fa) continue; 37 if(vis[Eiv]) {chck = true; return;} 38 check(Eiv, s); 39 if(chck) return; 40 } 41 } 42 43 void dfs(int s, int fa) 44 { 45 for(int i = head[s]; i != -1; i = edge[i].nxt){ 46 int Eiv = edge[i].v; 47 if(Eiv != fa){ 48 dis[Eiv] = dis[s] + 1; 49 bb[Eiv] = dis[Eiv]; 50 if(dis[Eiv] > ans_max) {ans_max = dis[Eiv]; ans_index = Eiv;} //更新樹的直徑 51 dfs(Eiv, s); 52 bb[s] = max(bb[s], bb[Eiv]); //更新當前結點最長路徑 53 } 54 } 55 } 56 57 int main() 58 { 59 int T_case = 0; 60 while(~scanf("%d", &N) && N){ 61 scanf("%d", &M); 62 init(); 63 for(int m = 1, u, v; m <= M; m++){ 64 scanf("%d%d", &u, &v); 65 AddEdge(u, v); 66 AddEdge(v, u); 67 } 68 69 if(M > N-1) chck = true; 70 if(!chck) check(1, 0); //判斷是否存在環 71 if(!chck){ 72 for(int i = 1; i <= N; i++){ 73 if(!vis[i]){chck = true; break;} //判斷所有點是否聯通 74 } 75 if(!chck){ 76 dis[1] = 0; 77 dfs(1, 0); //第一次dfs找出樹的直徑所在的點 78 dis[ans_index] = 0; 79 dfs(ans_index, 0); //第二次dfs找出樹的直徑 80 for(int i = 1; i <= N; i++){ //判斷所有的點是否滿足條件 81 bool flag = false; 82 if(bb[i] == ans_max) continue; //在直徑上 83 for(int k = head[i]; k != -1; k = edge[k].nxt){ //判斷不在樹的直徑上的點到樹的直徑距離是否為 1 84 if(bb[edge[k].v] == ans_max) {flag = true; break;} 85 } 86 if(!flag) {chck = true; break;} 87 } 88 } 89 } 90 if(chck) printf("Graph %d is not a caterpillar.\n", ++T_case); 91 else printf("Graph %d is a caterpillar.\n", ++T_case); 92 } 93 return 0; 94 }
