AcWing 1072. 树的最长路径(树形dp)

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

在这里插入图片描述
在这里插入图片描述
参考 彩色铅笔

题意:

在一棵全为无向边,且带边权的树中找到一条长度最大的路径并输出最大长度

思路:

这是一道比较经典的 树形DP 题目,我们来一步步来剖析这个问题

我们知道:树上 任意两点 的路径是 唯一确定的,因此我们可以暴力枚举 起点终点 找出最长路径

如果这样做的话,我们来思考一下时间复杂度:

  • 枚举 起点终点 O ( n 2 ) O(n^2) O(n2)
  • 找出两点之间的路径长度 — O ( l o g ⁡ n ) O(log⁡n) 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 ≠ c2f[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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值