0
点赞
收藏
分享

微信扫一扫

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

Xin_So 2022-03-23 阅读 38

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

题意:

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

思路:

这是一道比较经典的 树形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 ) 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;
}
举报

相关推荐

翻转树边(树形dp)

树形 DP

树形DP.

树形dp总结

树形dp 笔记

树形DP练习

树形DP专题

0 条评论