*总结的别人博客
树的直径(Diameter)是指树上的最长简单路。
直径的求法:两遍BFS (or DFS)
任选一点u为起点,对树进行BFS遍历,找出离u最远的点v
以v为起点,再进行BFS遍历,找出离v最远的点w。则v到w的路径长度即为树的直径
*简单证明
于是原问题可以在O(E)时间内求出
关键在于证明第一次遍历的正确性,也就是对于任意点u,距离它最远的点v一定是最长路的一端。
如果u在最长路上,那么v一定是最长路的一端。可以用反证法:假设v不是最长路的一端,则存在另一点v’使得(u→v’)是最长路的一部分,于是len(u→v’) > len(u→v)。但这与条件“v是距u最远的点”矛盾。
如果u不在最长路上,则u到其距最远点v的路与最长路一定有一交点c,且(c→v)与最长路的后半段重合(why?),即v一定是最长路的一端
f(t, m)表示,在以t为根的一棵树中,选出包含根节点t的m个连通的结点,能够获得的最高的评分,然后我们的答案就是f(1, M)!
一般的思路会是这样子的,首先我要包含根节点,然后与根节点连通的结点最开始便是根节点的子结点,而所有选择的结点都要互相连通的话,那么如果选择某一棵子树中的结点的话就势必也需要选择这棵子树的根节点——所以就变成了一个规模小一些的子问题。比如在求解f(t, m)的时候,我先枚举t的第一个子结点t1中选出的结点数m1,然后枚举t的第二个子结点t2中选出的结点数m2……一直到t的最后一个子结点tk中选出的结点数mk,这样就有f(t, m) = max{f(t1, m1) + f(t2, m2) + …… + f(tk, mk)} + v(t),并且需要保证m1+m2+...+mk+1=m。
1 #include <stdio.h> 2 #include <memory.h> 3 4 #define MAX_N 100000 5 #define MAX_M 200000 6 //同一边的两端 7 int U[MAX_M]; 8 int V[MAX_M]; 9 //记录连接后的子树的边 10 int GFirst[MAX_M]; 11 int GNext[MAX_M]; 12 13 int ans = 0; 14 15 int treeDiameter(int st, int prev) { //prev是st的父亲 16 //最长的距离和次长距离 17 int first = 0; 18 int second = 0; 19 //访问st的所有连接的边 20 for(int e=GFirst[st];e!=-1;e=GNext[e]) { 21 22 int u = U[e]; 23 int v = V[e]; 24 v = (u == st ? v : u);//记录父亲结点 防止重复访问 25 26 if(v == prev) 27 continue; 28 29 int d = treeDiameter(v, st)+1; 30 //更新最长与次长距离 31 if(d > first) { 32 second = first; 33 first = d; 34 } 35 else if(d > second) { 36 second = d; 37 } 38 } 39 40 if(first + second > ans) { 41 ans = first + second; 42 } 43 44 return first; 45 } 46 47 48 void add_edge(int u, int v, int e) { 49 //第e条边连接的两个结点 50 U[e] = u; 51 V[e] = v; 52 GNext[e] = GFirst[u]; 53 GFirst[u] = e; 54 } 55 56 int main() 57 { 58 memset(GFirst, -1, sizeof(GFirst)); 59 int N; 60 61 scanf("%d", &N); 62 63 64 for(int i=0,e=0;i<N-1;i++) { 65 66 int u,v; 67 scanf("%d %d", &u, &v); 68 u--, v--; 69 //建树 70 add_edge(u, v, e); 71 e++; 72 73 add_edge(v, u, e); 74 e++; 75 } 76 77 treeDiameter(0, -1); 78 79 printf("%d", ans); 80 81 return 0; 82 } 83 84 DFS
一张无向图中,给定图中一点,以最短路径长度当作距离,找出改点最远的一点,这两点之间的距离就叫做偏心距。
要计算一张无向图的半径,只要先算好两点之间最短路径,然后按定义来算即可。
先用floyd 再搜索最长的路径(即为直径)。
1 int d[10][10]; // adjacency matrix 2 int ecc[10]; // 各點的偏心距 3 4 void diameter_radius() 5 { 6 // Floyd-Warshall Algorithm 7 for (int k=0; k<10; ++k) 8 for (int i=0; i<10; ++i) 9 for (int j=0; j<10; ++j) 10 d[i][j] = min(d[i][j], d[i][k] + d[k][j]); 11 12 // 計算偏心距 13 memset(ecc, 0x7f, sizeof(ecc)); 14 for (int i=0; i<10; ++i) 15 for (int j=0; j<10; ++j) 16 ecc[i] = min(ecc[i], d[i][j]); 17 18 // 計算直徑和半徑 19 int diameter = 0; 20 int radius = 1e9; 21 for (int i=0; i<10; ++i) 22 { 23 diameter = max(diameter, ecc[i]); 24 radius = min(radius , ecc[i]); 25 } 26 27 /* 28 // 直徑也可以這樣算 29 for (int i=0; i<10; ++i) 30 for (int j=0; j<10; ++j) 31 diameter = max(diameter, d[i][j]); 32 */ 33 }
一棵樹的各種直徑一定會相交在同一點(同一群點)。
1.
反證法。
現在有兩條分開的直徑,
可是一棵樹上各點都得連通,
所以這兩條分開的直徑,中間一定有某處互相連接,
一旦連接起來,勢必變成更長的直徑,矛盾。
故所有直徑必相交。
2.
反證法。
現在已有兩條直徑相交在某一點,
如果另外一條直徑與這兩條直徑相交在另一點,
勢必變成更長的直徑,矛盾。
故所有直徑必相交在同一點(同一群點)。