参考 彩色铅笔
题意:
在一棵全为无向边,且带边权的树中找到一条长度最大的路径并输出最大长度
思路:
这是一道比较经典的 树形DP 题目,我们来一步步来剖析这个问题
我们知道:树上 任意两点 的路径是 唯一确定的,因此我们可以暴力枚举 起点 和 终点 找出最长路径
如果这样做的话,我们来思考一下时间复杂度:
- 枚举 起点 和 终点 — O ( n 2 ) O(n^2) O(n2)
- 找出两点之间的路径长度 — O ( l o g n ) O(logn) O(logn)
但是光是枚举 起点 和 终点,时间复杂度 就直接拉满了,显然这种做法不可取
既然这 O ( n 2 ) O(n^2) O(n2) 条路径不能 一一枚举,那么有什么方式可以把他们 分类枚举 呢?
我们考虑换一种 枚举方式:枚举路径的 起点和终点 → 枚举路径的 中间节点
我们先讨论一下,对于给定拓扑结构的树里的任意节点,经过他的路径有哪些:
观察我标出的 红色节点,那么经过他的路径有:
- 以其 子树中的某个节点 作为 起点,以他作为 终点 的 粉色路径
- 以其 子树中的某个节点 作为 起点,以 子树中的某个节点 作为 终点 的 蓝色路径
- 以其 子树中的某个节点 作为 起点,以 非其子树的节点 作为 终点 的 橙色路径
对于第 1
种情况,我们可以直接递归处理其子树,找出到当前子树根节点最长的路径长度即可
对于第 2
种情况,我们在处理第 1
种情况时,顺便找出 1
类路径的 次长路径
把 最长 和 次长 拼在一起,就是我们要的第 2
种情况
而对于第 3
种情况,我们可以把它归类为其 祖先节点 的第 1,2
种情况,让其 祖先节点 去处理即可
把上述两类路径的分析,用 闫氏DP分析法 写出,就是如下形式:
闫氏DP分析法
状态表示——集合 f[1/2,i]
:以节点 i
为根节点的子树 中,从 子树某个节点 到 节点i
的 最长路 为 f[1,i]
,次长路 为 f[2,i]
的 路径集合
状态表示——属性 f[1/2,i]
:长度的 最大值 Max
状态计算——f[1/2,i]
分两种情况:最长路转移 和 次长路转移
① f[1,i] = max(f[1,c1] + w[i,c1])
② f[2,i] = max(f[1,c2] + w[i,c2])
(其中,c1、c2
皆为节点 i
的子节点,满足 c1 ≠ c2
且 f[2,i] ≤ f[1,i]
)
根据定义,显然对于 最终的答案 res = max(f[1,i] + f[2,i])(i = 1、2、3、......、n)
,即无向树中的最长路径
时间复杂度:
O ( n ) O(n) O(n)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10, M = N<<1;
int n;
int h[N], e[M], ne[M], w[M], idx;
bool st[N];
int f1[N], f2[N];
int res = -1;
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int dfs(int u, int father)
{
f1[u] = 0, f2[u] = 0, st[u] = true;
for(int i=h[u]; ~i; i=ne[i])
{
int j = e[i];
if(!st[j]&&j!=father)
{
dfs(j, u);
//最长路转移 和 次长路转移
if(f1[u]<=f1[j]+w[i]) f2[u] = f1[u], f1[u] = f1[j] + w[i];// 结合定义,如果 f1[u]<=f1[j]+w[i],代表 f1[u] 可被更新,此时更新前的 f1[u] 要用 f2[u] 替代
else if(f2[u]<f1[j]+w[i]) f2[u] = f1[j] + w[i];// 如在上述条件不符合的情况下,有 f2[u]<f1[j]+w[i],那么此时只需要更新 f2[u] 即可
}
}
res = max(res, f1[u]+f2[u]); //更新最大值
}
int main()
{
cin>>n;
memset(h, -1, sizeof h);
for(int i=0; i<n-1; ++i)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs(1, -1);
printf("%d\n", res);
return 0;
}