求树中最长的一条路径,也就是在树中找到两个点,使其距离最远。
样例输入n个点,m条边。接下去m行,每行代表点a和b之间有一条边,边权是c,最后的字母代表边的方向(在这题没用)。
数据范围n <= 100000
1、遍历两次树,bfs或dfs均可(只能处理边权为正的)
参考链接
方法:先从任意一点P出发,找离它最远的点Q,再从点Q出发,找离它最远的点W,W到Q的距离就是是的直径
证明如下:
①若P已经在直径上,根据树的直径的定义可知Q也在直径上且为直径的一个端点
②若P不在直径上,我们用反证法,假设此时WQ不是直径,AB是直径
—>若AB与PQ有交点C,由于P到Q最远,那么PC+CQ>PC+CA,所以CQ>CA,易得CQ+CB>CA+CB,即CQ+CB>AB,与AB是直径矛盾,不成立,如下图(其中AB,PQ不一定是直线,画成直线是为了方便):
—>若AB与PQ没有交点,M为AB上任意一点,N为PQ上任意一点。P到Q最远,NP+NQ>NQ+MN+MB,同时减掉NQ,得NP>MN+MB,所以有NP+MN > NP-MN > MB,所以NP+MN+MA > MB+MA,即NP+MN+MA > AB,与AB是直径矛盾,所以这种情况也不成立,如下图:
这边采用bfs进行遍历,遍历模板
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = head[t]; i != -1; i = nxt[i])
{
int j = val[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
该题代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 100005;
int n, m;
int lnk[N], val[N], nxt[N], head[N], idx;
queue<pair<int, int>> q;
bool visit[N];
void add(int a, int b, int w) {
lnk[idx] = b;
val[idx] = w;
nxt[idx] = head[a];
head[a] = idx ++ ;
}
pair<int, int> bfs(int root) {
memset(visit, 0, sizeof visit);
int res = 0, len = 0;
q.push(make_pair(root, 0));
visit[root] = true;
while(!q.empty()) {
pair<int, int> p = q.front();
q.pop();
for (int i = head[p.first]; i != -1; i = nxt[i]) {
pair<int, int> node = make_pair(lnk[i], p.second + val[i]);
if (!visit[node.first]) {
q.push(node);
visit[node.first] = true;
if (len < node.second) {
len = node.second;
res = node.first;
}
}
}
}
return make_pair(res, len);
}
int main() {
scanf("%d%d", &n, &m);
int a, b, c;
char ch;
memset(head, -1, sizeof head);
idx = 0;
for (int i = 1; i <= m; i ++ ) {
scanf("%d%d%d %c", &a, &b, &c, &ch);
add(a, b, c);
add(b, a, c);
}
pair<int, int> x = bfs(1);
pair<int, int> y = bfs(x.first);
printf("%d\n", y.second);
return 0;
}
2、树形DP做法(边权为负也可处理)
如果子问题表示为:以这个点为根的子树的直径最大值。
因为最长的直径和这个点没有直接关联,因此难以从子问题递推。
将子问题表示为:以这个点i为根,往下走的路径的最大值,记作f[i]
。
将每个点的f[i]算出来,这样就可以求得经过每个点的直径最大值。例如下图所示,蓝色线段是所有经过点i的路径中最大的。
那么f[i]等于它所有子节点最大的两个f之和,即f[i] = f[a] + f[c] + w[i, a] + w[i, c]
采用dfs进行遍历,则不需要存储f。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100005;
int n, m, ans;
int lnk[2 * N], val[2 * N], nxt[2 * N], head[2 * N], idx;
bool visit[N];
void add(int a, int b, int w) {
lnk[idx] = b;
val[idx] = w;
nxt[idx] = head[a];
head[a] = idx ++ ;
}
int dfs(int node) {
visit[node] = true;
int dis = 0, d1 = 0, d2 = 0;
for (int i = head[node]; i != -1; i = nxt[i]) {
int son = lnk[i];
if (!visit[son]) {
dis = dfs(son) + val[i];
if (d1 <= dis) d2 = d1, d1 = dis;
else if (d2 < dis) d2 = dis;
}
}
ans = max(ans, d1 + d2);
return d1;
}
int main() {
scanf("%d%d", &n, &m);
int a, b, c;
char ch;
memset(head, -1, sizeof head);
idx = 0;
for (int i = 1; i <= m; i ++ ) {
scanf("%d%d%d %c", &a, &b, &c, &ch);
add(a, b, c);
add(b, a, c);
}
dfs(1);
printf("%d\n", ans);
return 0;
}