

参考 彩色铅笔
题意:
在一棵全为无向边,且带边权的树中找到一条长度最大的路径并输出最大长度
思路:
这是一道比较经典的 树形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 + m ) O(n + m) O(n+m)
代码:
在main函数中我们任取一个点为根节点,由于是无向边,因此我们加边的时候是向两个方向建边的,因此在dfs的时候需要判断:只能往下搜索,不能往上搜索,否则会陷入死循环,因此我们可以在dfs函数中新添加一个参数 father,代表当前节点的父节点,显然根节点 1 没有父节点,因此传入一个不存在的整数 -1
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
int h[N], e[N<<1], ne[N<<1], w[N<<1], idx;
int n;
int dp1[N], dp2[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)
{
dp1[u] = dp2[u] = 0; //初始化
for(int i=h[u]; ~i ; i=ne[i])
{
int j = e[i];
if(j==father) continue;
int d = dfs(j, u) + w[i];
//最长路转移 和 次长路转移
if(dp1[u] <= d) dp2[u] = dp1[u], dp1[u] = d;// 结合定义,如果 dp1[u]<dp1[j]+w[i],代表 dp1[u] 可被更新,此时更新前的 dp1[u] 要用 dp2[u] 替代
else if(dp2[u] < d) dp2[u] = d;// 如在上述条件不符合的情况下,有 dp2[u]<dp1[j]+w[i],那么此时只需要更新 dp2[u] 即可
}
res = max(res, dp1[u] + dp2[u]);//更新最大值
return dp1[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);
cout<<res<<endl;
return 0;
}

本文解析了一道经典树形动态规划问题,介绍如何通过闫氏DP法避免O(n^2)的时间复杂度,利用子树结构和路径转移规则,以O(n+m)的时间复杂度找到一棵带边权无向树中长度最长的路径。通过递归和状态转移方程,实现高效求解并给出C++代码实例。
1706

被折叠的 条评论
为什么被折叠?



