学习大佬:树的直径求法及证明
定义:
一棵树的直径就是这棵树上存在的最长路径。
给定一棵树,树中每条边都有一个权值,树中两点之间的距离定义为连接两点的路径边权之和。树中最远的两个节点之间的距离被称为树的直径,连接这两点的路径被称为树的最长链。后者通常也可称为直径,即直径是一个数值概念,也可代指一条路径。
求法:
一、树形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 }