【ACWing】1073. 树的中心

题目地址:

https://www.acwing.com/problem/content/1075/

给定一棵树,树中包含 n n n个结点(编号 1 ∼ n 1\sim n 1n)和 n − 1 n−1 n1条无向边,每条边都有一个权值。请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。

输入格式:
第一行包含整数 n n n。接下来 n − 1 n−1 n1行,每行包含三个整数 a i , b i , c i a_i,b_i,c_i ai,bi,ci,表示点 a i a_i ai b i b_i bi之间存在一条权值为 c i c_i ci的边。

输出格式:
输出一个整数,表示所求点到树中其他结点的最远距离。

数据范围:
1 ≤ n ≤ 10000 1≤n≤10000 1n10000
1 ≤ a i , b i ≤ n 1≤a_i,b_i≤n 1ai,bin
1 ≤ c i ≤ 1 0 5 1≤c_i≤10^5 1ci105

思路是DFS,枚举每个顶点,不妨设正在枚举 x x x,求出从该顶点出发的最长的路径的长度,然后找到那个最长路径最短的那个节点。我们任取一个顶点作为树根,然后将整棵树看成一个有根树,这样可以将所有路径按照其经过的最高点来分类,然后枚举顶点 x x x,求出以 x x x为最高点的最长路径长度。设 f ( x ) f(x) f(x)是从 x x x向下走的所有路径里最长的路径,设 g ( x ) g(x) g(x)是从 x x x向上走的所有路径里最长的路径。设 x x x的孩子有 c 1 , c 2 , . . . , c k c_1,c_2,...,c_k c1,c2,...,ck,那么 f ( x ) = ∣ ( x , c i ) ∣ + f ( c i ) f(x)=|(x,c_i)|+f(c_i) f(x)=(x,ci)+f(ci),这个可以直接以DFS求解。但是对于 g ( x ) g(x) g(x)则稍微复杂一些,设 p x p_x px x x x的父亲,那么 g ( x ) g(x) g(x)肯定是先向上走一步的,但是接下来怎么走就得分类了。首先有可能继续向上走,那么就是 ∣ ( x , p x ) ∣ + g ( p x ) |(x,p_x)|+g(p_x) (x,px)+g(px);也有可能是向下走,但是向下走不能走回 x x x,我们可以这样考虑,如果 f ( p x ) f(p_x) f(px)是不通过 x x x的,那么向下走的那条路可以走取到 f ( p x ) f(p_x) f(px)的那条;否则的话就要找到去掉通过 x x x的路径之后,剩余路径里的最长路径。所以我们在算 f f f的时候,还要记录一下从 x x x向下走的路径里最长和第二长的路径分别经过了哪个儿子(注意这里的最长和第二长是按经过哪个儿子来分类的,即求 f ( c i ) f(c_i) f(ci)取到最大和次大值的那个 c i c_i ci)。代码如下:

#include <iostream>
#include <cstring>
using namespace std;

// 边数是顶点数乘以2
const int N = 10010, M = 2 * N;
int n;
int h[N], e[M], ne[M], w[M], idx;
// d1[x]是从x向下走的第一长的路径长度,d2[x]是从x向下走的第二长的路径长度;
// p1[x]是从x向下走的第一长的路径长度经过的孩子节点编号,p2[x]是从x向下走的
// 第二长的路径长度经过的孩子节点编号;up[x]是从x向上走的最长路径长度;
// 以上所说路径都是不自交的路径
int d1[N], d2[N], p1[N], p2[N], up[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_d(int u, int parent) {
    
    
	// 枚举u的孩子
    for (int i = h[u]; i != -1; i = ne[i]) {
    
    
        int j = e[i];
        // 不走回头路
        if (j == parent) continue;
		
		// 递归求解从j出发向下的最长路径长度
        int d = dfs_d(j, u) + w[i];
        if (d >= d1[u]) {
    
    
            d2[u] = d1[u], d1[u] = d;
            p2[u] = p1[u], p1[u] = j;
        } else if (d > d2[u]) {
    
    
            d2[u] = d;
            p2[u] = j;
        }
    }

    return d1[u];
}

// 求从u出发向上走的最长路径长度
void dfs_u(int u, int parent) {
    
    
    for (int i = h[u]; i != -1; i = ne[i]) {
    
    
        int j = e[i];
        if (j == parent) continue;
		
		// 如果从u出发向下走的最长路就是通过j的,那么就应该累加从u向上走的
		// 最长路长度和从u向下走的第二长路长度之更大者;否则累加从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_u(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_d(1, -1);
    dfs_u(1, -1);

    int res = 0x3f3f3f3f;
    for (int i = 1; i <= n; i++) res = min(res, max(d1[i], up[i]));

    cout << res << endl;

    return 0;
}

时空复杂度 O ( n ) O(n) O(n)

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/114713481