树的直径:树上最远两点(叶子结点)的距离。那么怎么求树的直径呢?
- 方法一:暴力求解,从每个点开始遍历图,可以得到每个点v所在的最长路径max1和次长路径max2,注意的是最长路径和次长路径除了点v没有其他公共结点。如下图所示,经过点A的最长路径应该是下图左所示的路径,而非下图右所示的路径,通过A的最长路径必须是来自不同分支。方法一,暴力破解,每个点做一趟DFS的话,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
- 方法二:从树上任意点u开始DFS(BFS)遍历图,得到距离u最远的结点v,然后从v点开始DFS遍历图,得到距离v最远的结点w, 则v、w之间的距离就是树的直径。
证明:假设路径v-w为树的直径
1)u位于v-w所在的路径上,如下图左图所示,那么从u点做DFS能访问到的最远点必然是v或w, 否则假设访问到的最远点为x, 如下图所示,有 d i s t ( u , x ) ≥ d i s t ( u , v ) , d i s t ( u , x ) ≥ d i s t ( u , w ) dist(u,x) \geq dist(u,v), dist(u,x) \geq dist(u,w) dist(u,x)≥dist(u,v),dist(u,x)≥dist(u,w), 分两种情况讨论:
a) 如果只取大于号, d i s t ( u , x ) > d i s t ( u , v ) , d i s t ( u , x ) > d i s t ( u , w ) dist(u,x) > dist(u,v) , dist(u,x) > dist(u,w) dist(u,x)>dist(u,v),dist(u,x)>dist(u,w),
那么 d i s t ( u , x ) + d i s t ( u , v ) = d i s t ( v , x ) > d i s t ( u , v ) + d i s t ( u , w ) = d i s t ( v , w ) dist(u,x) + dist(u,v) = dist(v,x) > dist(u,v) + dist(u,w) = dist(v,w) dist(u,x)+dist(u,v)=dist(v,x)>dist(u,v)+dist(u,w)=dist(v,w), 那么v-w不是树的是直径,跟假设矛盾。
b) 如果取大于等于号, d i s t ( u , x ) ≥ d i s t ( u , v ) , d i s t ( u , x ) ≥ d i s t ( u , w ) dist(u,x) \geq dist(u,v) , dist(u,x)\geq dist(u,w) dist(u,x)≥dist(u,v),dist(u,x)≥dist(u,w)
假设 d i s t ( u , x ) = d i s t ( u , v ) dist(u,x) = dist(u,v) dist(u,x)=dist(u,v) 那么 d i s t ( x , w ) = d i s t ( v , w ) dist(x,w) = dist(v,w) dist(x,w)=dist(v,w), 这样也没问题,树的直径不唯一而已,那么x依然位于树的直径的一个端点上。
2)u不位于v-w所在的路径上,如下图右图所示,那么有 d i s t ( u , x ) > d i s t ( u , y , v ) , d i s t ( u , x ) > d i s t ( u , y , w ) dist(u,x) > dist(u,y,v) , dist(u,x) > dist(u,y,w) dist(u,x)>dist(u,y,v),dist(u,x)>dist(u,y,w), 这里y是u到路径[v-w]的任意点,那么就有 d i s t ( u , x ) + d i s t ( u , y , w ) = d i s t [ x , w ] > d i s t ( v , y ) + d i s t ( y , w ) = d i s t ( v , w ) dist(u,x) + dist(u,y,w) = dist[x,w] > dist(v,y) + dist(y,w) = dist(v,w) dist(u,x)+dist(u,y,w)=dist[x,w]>dist(v,y)+dist(y,w)=dist(v,w), 那么说明那么v-w不是树的是直径,跟假设矛盾。
综上,方法二正确,且复杂度为2趟DFS,因此复杂度为 O ( n ) O(n) O(n)。比方法一快很多。
模板如下:
#include <iostream>
#include <cstring>
using namespace std;
//maxv:源点能到的最远点,maxdis:最远点对应的距离,
const int maxn = 1e4 + 5;
struct Edge { int to, next, w; }edges[2 * maxn];
int head[maxn], maxdis,maxv, ne;
void add(int u, int v, int w) {
edges[ne] = { v, head[u], w };
head[u] = ne++;
}
//u:dfs的源点,f: u点的父节点,d2s:u点到源点的距离
void dfs(int u, int f, int d2s) {
if (maxdis < d2s){
maxdis = d2s;
maxv = u;
}
for (int e = head[u]; e != -1; e = edges[e].next) {
int v = edges[e].to, w = edges[e].w;
if (v == f) continue; //父节点已经访问过,防止重复遍历,相反孩子不会重复遍历。
dfs(v, u, d2s + w);
}
}
int main() {
int e, u, v, w, s;
cin >> e;
memset(head, -1, sizeof(head));
for (int i = 1; i <= e; i++) {
cin >> u >> v >> w;
add(u, v, w), add(v, u, w);
}
dfs(1, -1, 0); //从结点1开始遍历,找到最远点maxv及对应的最远距离maxdis
maxdis = 0;
dfs(maxv, -1, 0);//从结点maxv开始遍历,找到最远点对应的距离maxdis
cout << maxdis << endl;
return 0;
}
重要性质:
树上任意点能到的最远点,一定是树的直径的某个端点。通过方法二的反证法可以推出此结论。
例题:HDOJ 2196:http://acm.hdu.edu.cn/showproblem.php?pid=2196
解法一:根据上述结论,先求出树的直径,然后从直径的两个端点u和v分别DFS整棵树,对于每个结点得到两个距离d[i].u和d[i].v, 二者的最大值即是i点能到的最远点的距离。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e4 + 5;
struct Edge { int to, next, w; }edges[2 * maxn];
int head[maxn], vis[maxn], d1[maxn], d2[maxn], maxv, n, ne;
void add(int u, int v, int w) {
edges[ne] = { v, head[u], w };
head[u] = ne++;
}
void dfs(int u, int f, int depth, int* d) {
d[u] = depth;
if (d[maxv] < d[u]) maxv = u;
for (int e = head[u]; e != -1; e = edges[e].next) {
int v = edges[e].to, w = edges[e].w;
if (v == f) continue;
dfs(v, u, depth + w, d);
}
}
int main() {
int n, u, v, w, s;
while (~scanf("%d", &n)) {
memset(head, -1, sizeof(head));
ne = 0;
for (int i = 2; i <= n; i++) {
scanf("%d%d", &v, &w);
add(i, v, w), add(v, i, w);
}
s = 1, d1[maxv] = 0;
dfs(s, -1, 0, d1);
s = maxv, d1[maxv] = 0;
dfs(s, -1, 0, d1);
s = maxv, d2[maxv] = 0;
dfs(s, -1, 0, d2);
for (int i = 1; i <= n; i++)
printf("%d\n", max(d1[i], d2[i]));
}
return 0;
}
解法二:树上DP,讲解参见:
1)https://www.cnblogs.com/WABoss/p/5267488.html?tdsourcetag=s_pcqq_aiomsg
2)https://blog.csdn.net/shuangde800/article/details/9732825
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e4 + 5;
struct Edge { int to, next, w; }edges[2 * maxn];
int head[maxn], dis[maxn][3], maxson[maxn], vis[maxn], n, tot;
void add(int u, int v, int w) {
edges[tot] = { v, head[u], w };
head[u] = tot++;
}
void dfs1(int u) {
vis[u] = 1; dis[u][0] = dis[u][1] = 0;
for (int e = head[u]; e != -1; e = edges[e].next) {
int v = edges[e].to, w = edges[e].w;
if (vis[v]) continue;
dfs1(v);
int dist = dis[v][0] + w;
if (dist >= dis[u][0]) {
dis[u][1] = dis[u][0]; dis[u][0] = dist;
maxson[u] = v;
}
else if (dist > dis[u][1])
dis[u][1] = dist;
}
}
void dfs2(int u) {
vis[u] = 1;
for (int e = head[u]; e != -1; e = edges[e].next) {
int v = edges[e].to, w = edges[e].w;
if (vis[v]) continue;
if (v != maxson[u]) dis[v][2] = max(dis[u][0], dis[u][2]) + w;
else dis[v][2] = max(dis[u][1], dis[u][2]) + w;
dfs2(v);
}
}
void init() {
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(dis, 0, sizeof(dis));
memset(maxson, 0, sizeof(maxson));
tot = 0;
}
int main() {
int u, v, w;
while (~scanf("%d", &n)) {
init();
for (int i = 2; i <= n; i++) {
scanf("%d%d", &v, &w);
add(i, v, w);
add(v, i, w);
}
dfs1(1);
memset(vis, 0, sizeof(vis));
dfs2(1);
for (int i = 1; i <= n; i++)
printf("%d\n", max(dis[i][0], dis[i][2]));
}
return 0;
}