AcWing 1073. 树的中心

题目传送门

一、思路分析

这是一道很好的树形\(dp\)题,即用到了用子节点更新父节点信息的普遍情况,又用到了用父节点更新子节点的情况,所以掌握这一道题是很有必要的,废话不多说,下面开始讲讲我对\(y\)总的思路的理解:

首先分析题目,其实是要我们把每一个点到其他点的最长距离求出来,再求一个其中最短的就可以了,我们来分析一下每一个点可以在树上怎么走,其实就是向上和向下走。

我们用 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 <bits/stdc++.h>

using namespace std;
/**
 假设当前点是j点,上一结点是u点,计算经过j点的到所有点的最长距离res = max(d1[j],up[j])
 (1)当u点往下走的最大长度经过j,则需要用到次大长度
    up[j] = max(up[u], d2[u]) + w[i]
 (2)当u点往下走的最大长度不经过j,则
    up[j] = max(up[u], d1[u]) + w[i]
 */
//求树的中心
const int N = 10010;
const int M = N * 2;
const int INF = 0x3f3f3f3f;

int n;
int h[N], e[M], w[M], ne[M], idx;
int d1[N];   //d1[u]:表示u点向下走的最大长度
int d2[N];   //d2[u]:表示u点向下走的次大长度
int up[N];   //up[u]:表示u点向上走的最大长度

int p1[N];   //与d1配套使用,记录在取得d1的更新时,选择的哪个子节点
int leaf[N]; //u节点是不是叶子

//邻接表添加边[模板]
void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

//从u节点向下走,求最长路径,与前一题1072是一样的思路。只不过这里需要记录次长长度,还需要标识叶子
//[子节点更新父节点]
//普遍情况
int dfs_d(int u, int father) {
    //最长和次长路径初始化为负无穷,可以解决路径长度有负数的问题
    d1[u] = d2[u] = -INF;
    for (int i = h[u]; i != -1; i = ne[i]) {//遍历u的每一条出边
        int j = e[i];             //i表示从u->j的边,j表示u可以到达的一个节点
        if (j == father) continue;//因为是无向图,目前在思考向下走,所以向上去的边不讨论
        //讨论走这条边i的话,会不会取得更大的路径长度
        int d = dfs_d(j, u) + w[i];
        //打擂台更新最长长度
        if (d > d1[u]) {
            d2[u] = d1[u], d1[u] = d; //更新最长长度和次长长度
            p1[u] = j;                //记录最长长度是从哪个节点更新而来
        } else if (d > d2[u]) d2[u] = d;//是否可以更新次长长度,因为最长长度可能是由j更新而来,
        // 一会在用u的信息更新j时,要避开自己这条路线,所以预备了一个第二选择路线
    }
    //如果最大长度没有被更新,就表示是叶子节点
    if (d1[u] == -INF) {
        d1[u] = d2[u] = 0;//叶子节点的最长长度和次长长度是0,需要修改一下,否则如果还是-INF,???
        leaf[u] = 1;//标识出来
    }
    //返回最大长度
    return d1[u];
}

//向上走求最长长度(其实这么说并不严谨)
//[父节点更新子节点]
void dfs_u(int u, int father) {
    for (int i = h[u]; i != -1; i = ne[i]) {//遍历u的每一条出边
        int j = e[i];                       //i表示从u->j的边,j表示u可以到达的一个节点
        if (j == father) continue;          //因为是无向图,目前在思考向上走,所以向下去的边不讨论
        //如果u的向下最长长度是通过j获取到的,那么需要使用次长长度更新up[j]
        if (p1[u] == j) up[j] = max(up[u], d2[u]) + w[i];
        else up[j] = max(up[u], d1[u]) + w[i];
        //
        dfs_u(j, u);
    }
}

int main() {
    cin >> n;
    //邻接表初始化
    memset(h, -1, sizeof h);
    //无向图,n个顶点,n-1条边
    for (int i = 1; i <= n - 1; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    //从任意一个点出发,走一遍dfs整个树,收集每个节点的向下走最大长度和向上走最大长度,最后再遍历找出两者max最小的一个
    //向下求最大长度
    dfs_d(1, -1);
    //向上求最大长度
    dfs_u(1, -1);

    //???
    int res = d1[1];
    for (int i = 2; i <= n; i++)
        if (leaf[i]) res = min(res, up[i]);
        else res = min(res, max(d1[i], up[i]));
    //输出
    printf("%d\n", res);
    return 0;
}

上一篇:靶机简单渗透测试


下一篇:AcWing 1075. 数字转换