定义
设树上任意两点x, y之间的距离为dis(x, y),那么树的直径就是求这棵树中的Max {dis(x,y)}
方法介绍
1.树形dp
优点:代码简单,可以求出以当前节点为根时的最长链,O(N)时间复杂度
缺点:不能求出最长链的路径
2.两次dfs(或bfs)
优点:可以求出路径
缺点:时间复杂度O(2N), 在负权边中无法使用
求解方法
1.树形dp
我们假设dis[v]为以v为根的子树到u的最长距离,那么取v的父节点u,将v和u的子树到各自的最长距离拼接在一起,即ans = max(ans, dis[v] + dis[u] + e[i].vi),并在回溯的过程中维护dis[u] = max(dis[u], dis[v] + e[i].vi)即可
2.两次dfs
我们先从任意一个点出发,遍历整颗树,找到距离出发点最远的一个点p,再从p出发遍历整棵树,找到距离p最远的q点,这时,p到q即为树的直径
下面给出简单的证明
1.如果出发点在直径的一个端点上,那么找到的Q为最远点,很显然PQ为直径
2.加入出发点不在直径的端点上,可以分两种情况
(1) 如下图,假设AB为直径,且PQ与AB不相交。
已知PM+QM>PM+MN+NB(PQ距离最远) 得出MQ>MN+NB,易得QM+MN>NB,因此MQ+NM+AN>AN+NB,所以AB不是直径
(2)如下图,假设AB为直径,且AB交PQ于O点。
PO+OQ>PO+OB 所以OQ>OB,所以OQ+OA>AO+OB 即OQ+AO>AB
AB不是直径
下面给出代码
树形dp
POJ2631
#include <iostream>
#include <algorithm>
#include <string.h>
#include <cstdio>
using namespace std;
const int M = 200010;
const int N = 100010;
int f[N], n, m, cnt, h[N], ans, dis[N];
struct edge {
int to;
int next;
int vi;
}e[M];
void add(int u, int v, int w) {
e[cnt].to = v;
e[cnt].vi = w;
e[cnt].next = h[u];
h[u] = cnt++;
}
void dp(int u, int fa) {
for (int i = h[u]; ~i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
dp(v, u);
ans = max(ans, dis[v] + dis[u] + e[i].vi);
dis[u] = max(dis[u], dis[v] + e[i].vi);
}
}
int x, y, z;
int main() {
memset(h, -1, sizeof h);
while (scanf("%d%d%d", &x, &y, &z) != EOF){
add(x, y, z);
add(y, x, z);
}
dp(1, 0);
cout << ans;
}
两次dfs以及路径
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int h[N], dis[N], step[N];
int n, m, cnt, p, ed;
struct edge{
int to;
int next;
int vi;
}e[N * 2];
void add(int u, int v, int w) {
e[cnt].to = v;
e[cnt].vi = w;
e[cnt].next = h[u];
h[u] = cnt++;
}
void dfs1(int u, int fa) {
if (dis[u] > dis[p]) p = u;
for (int i = h[u]; ~i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
dis[v] = dis[u] + e[i].vi;
dfs1(v, u);
}
}
void dfs2(int u, int fa) {
if (dis[u] > dis[ed]) ed = u;
for (int i = h[u]; ~i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
step[v] = u;
dis[v] = dis[u] + e[i].vi;
dfs2(v, u);
}
}
void PrintPath() {
int now = ed;
stack<int> stk;
do {
stk.push(step[now]);
now = step[now];
} while (now != p);
while (stk.size()) {
cout << stk.top() << "->";
stk.pop();
}
cout << ed << endl;
}
int main() {
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i < n; i++) {
int x, y, z;
cin >> x >> y >> z;
add(x, y, z), add(y, x, z);
}
dfs1(1, 0);
memset(dis, 0, sizeof dis);
dfs2(p, 0);
printf("the distance is:");
cout << dis[ed] << endl;
PrintPath();
}
/*
data:
7
1 2 2
2 4 1
2 5 3
1 3 4
3 6 1
3 7 5
/*
运行结果
HDU 2196
我们可以用三次dfs的做法
第一次求出p点,第二次从p点出发求出所有点到p的距离dis1,并选最远距离q出发算出所有距离q的距离dis2,最后的答案便是max(dis1,dis2)即到这两个端点的最远距离
此做法的正确性也可以证明,利用前面证明的,任意点出发所到的最远p一定是树的直径的两个端点之一,因此我们从两个端点出发遍历整棵树可以得出正确答案。
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int dis[N], dis1[N], dis2[N], h[N], d[N];
int cnt, n, m, p;
struct edge {
int to;
int next;
int vi;
}e[N*2];
void add(int u, int v, int w) {
e[cnt].to = v;
e[cnt].vi = w;
e[cnt].next = h[u];
h[u] = cnt++;
}
void dfs(int u, int fa, int *dis) {
if (dis[u] > dis[p]) p = u;
for (int i = h[u]; ~i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
dis[v] = max(dis[v], dis[u] + e[i].vi);
dfs(v, u, dis);
}
}
void init() {
memset(h, -1, sizeof h);
memset(dis, 0, sizeof dis);
memset(dis1, 0, sizeof dis1);
memset(dis2, 0, sizeof dis2);
cnt = 0;
}
int main() {
ios::sync_with_stdio(false);
while (cin >> n) {
init();
for (int i = 2; i <= n; i++) {
int x, y;
cin >> x >> y;
add(i, x, y), add(x, i, y);
}
dfs(1, 0, dis);
dfs(p, 0, dis1);
dfs(p, 0, dis2);
for (int i = 1; i <= n; i++) d[i] = max(dis1[i], dis2[i]);
for (int i = 1; i <= n; i++) cout << d[i] << endl;
}
}