Acwing 1073 树的中心:在树中找到一个点,使得该点到树中其他结点的最远距离最近 树形DP

题目描述

在这里插入图片描述

思路一

暴力换根,对于每个根求出最长路径,取一个最小值。
每次求最长路径,还是上题的一个思想。
但是时间复杂度是O(n*n) , 过了7个数据。

/*
    暴力换根,对于每个根求出最长路径,去一个最小值。
    每次求最长路径,还是上题的一个思想。
    但是时间复杂度是O(n*n) , 过了7个数据。
*/

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10010, M = N << 1, INF = 0x3f3f3f3f;

int n;
int h[N], e[M], w[M], ne[M], idx;
int d1[N], d2[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dfs(int u, int father)
{
    d1[u] = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == father) continue;
        int dist = dfs(j, u) + w[i];
        if (dist >= d1[u]) d1[u] = dist;
    }
    return d1[u];
}
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d", &n);
    for (int i = 1; i < n; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }

    int res = INF;
    for (int i = 1; i <= n; i ++ ) res = min(res, dfs(i, -1));
    printf("%d\n", res);
    return 0;
}

优化

考虑如何优化求解该问题的方法
思考一下:在确定树的拓扑结构后单独求一个节点的 最远距离 时,会在该树上去比较哪些 路径 呢?

从当前节点往下,直到子树中某个节点的最长路径
从当前节点往上走到其父节点,再从其父节点出发且不回到该节点的最长路径
此处就要引入 换根DP 的思想了

换根DP 一般分为三个步骤:

指定任意一个根节点

  • 一次dfs遍历,统计出当前子树内的节点对当前节点的贡献
  • 一次dfs遍历,统计出当前节点的父节点对当前节点的贡献,然后合并统计答案
  • 那么我们就要先 dfs 一遍,预处理出当前子树对于根的最大贡献(距离)和 次大贡献(距离)

处理 次大贡献(距离) 的原因是:

如果 当前节点 是其 父节点子树 的 最大路径 上的点,则 父节点子树 的 最大贡献 不能算作对该节点的贡献

因为我们的路径是 简单路径,不能 走回头路

然后我们再 dfs 一遍,求解出每个节点的父节点对他的贡献(即每个节点往上能到的最远路径

两者比较,取一个 max 即可

时间复杂度 为 T(2n)=O(n)

作者:一只野生彩色铅笔
链接:https://www.acwing.com/solution/content/65263/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

我们用 d1[u],d2[u],up[u],p1[u],p2[u]分别存一下需要的信息,这些数据存的是:

  • d1[u]:存下u节点向下走的最长路径的长度
  • d2[u]:存下u节点向下走的第二长的路径的长度
  • p1[u]:存下u节点向下走的最长路径是从哪一个节点下去的
  • p2[u]:存下u节点向下走的第二长的路径是从哪一个节点走下去的
  • up[u]:存下u节点向上走的最长路径的长度

向下走是很容易的,dfs就可以了,那怎么向上走呢?其实向上走就是求一个点的父节点的不走该节点的最长路径,其实我们知道了每一个节点向下走的长度就可以知道向上的最长路径了,一个子节点 j 的向上最长路径就是 它的父节点 u 的最长向上路径和最长向下路径取最大值,如果向下最长路径经过了 j 就改为第二长的向下路径,对应代码:

 if(p1[u]==j)up[j]=max(up[u],d2[u])+w[i];
 else up[j]=max(up[u],d1[u])+w[i];
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010, INF = 1e9;

// d1是最长路径,d2是次长路径
int d1[N], d2[N];
// up[u]:存下u节点向上走的最长路径的长度
int up[N];
// p1[u]:存下u节点向下走的最长路径是从哪一个节点下去的
// p2[u]:存下u节点向下走的第二长的路径是从哪一个节点走下去的
int p1[N], p2[N];
int h[N], ne[2*N], e[2*N], w[2*N], idx;
int n;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

// 返回u的最长向下路径
int dfs_down(int u, int father)
{
    // 求最大值,初始化为负无穷
    d1[u] = d2[u] = -INF;
    
    // 遍历所有临边
    for(int i = h[u]; ~ i; i = ne[i])
    {
        int j = e[i];
        // 跳过父节点
        if(j == father) continue;
        
        int dist = dfs_down(j, u) + w[i];
        // 更新最长路径
        if(dist >= d1[u])
        {
            d2[u] = d1[u], d1[u] = dist;
            // 记录一下最长子树路径是经过哪个子节点的
            p2[u] = p1[u], p1[u] = j; 
        }
        // 更新次长路径
        else if(dist > d2[u])
        {
            d2[u] = dist;
            p2[u] = j;
        }
    }
    
    // 如果没有改变过该点的距离,
    // 就证明这个点是叶节点
    if(d1[u] == -INF)
        d1[u] = d2[u] = 0;
    
    return d1[u];
}

// 子节点向上经过父节点u的最长路径
void dfs_up(int u, int father)
{
    // 对于u的每个子节点
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        
        if (j == father) continue;
        // 如果从父节点向下的最长路径经
        // 过了要更新的子节点,
        // 那么就用第二长的路径更新
        // 否则,直接更新。
        // j是子节点 u是父节点
        
        // p1[u]:存下u节点向下走的最长路径是从哪一个节点下去的
        if (p1[u] == j) up[j] = max(up[u], d2[u]) + w[i];
        else up[j] = max(up[u], d1[u]) + w[i];
        
        // 向下处理子节点
        dfs_up(j, u);
    }
}



int main()
{
    cin >> n;
    memset(h, -1, sizeof h);
    
    for(int i = 0; i < n - 1; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    
    // 随便找一个节点开始dfs
    // 向下
    dfs_down(1, -1);
    // 向上
    dfs_up(1, -1);
    
    // 找最小的
    int res = INF;
    for(int i=1; i<=n; i++) res = min(res, max(d1[i], up[i]));
    
    cout << res << endl;
    return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值