树的直径与重心

Posted lifehappy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树的直径与重心相关的知识,希望对你有一定的参考价值。

树的直径与重心

树的直径求解方法一

思路

先选取一个点rt作为根节点,dfs去找到一个最长路径的点U,然后通过这个点去dfs,找到路径最长的点V,U->V就是这课树的直径。

证明正确性:

假如rt在直径上的话,最长路径的点U一定是直径的一个端点,这一点是显然的。

假如rt不在直径上,那么从这个点出发也一定可以找到一条路到达直径上一点,接下来就如同上面一样了,无非就是在真正的dis上再加上一段固定的value,对我们最后的直径端点查找并无影响。

代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1e5 + 10;

int head[N], to[N], value[N], nex[N], cnt;
int n, ans, dis[N];

inline ll read() {
    ll x = 1, s = 0; char c;
    c = getchar();
    while(c < ‘0‘ || c > ‘9‘) {
        if(c == ‘-‘)    x = -1;
        c = getchar();
    }
    while(c >= ‘0‘ && c <= ‘9‘) {
        s = (s << 1) + (s << 3) + (c ^ 48);
        c = getchar();
    }
    return x * s;
}

void add(int x, int y, int w) {
    to[cnt] = y;
    nex[cnt] = head[x];
    value[cnt] = w;
    head[x] = cnt++;
}

void dfs(int rt, int fa) {
    for(int i = head[rt]; ~i; i = nex[i]) {
        if(to[i] == fa) continue;
        dfs(to[i], rt);
        dis[to[i]] = dis[rt] + value[i];
    }
}

int main() {
    n = read();
    int x, y, w;
    memset(head, -1, sizeof head);
    for(int i = 1; i < n; i++) {
        x = read(), y = read(), w = read();
        add(x, y, w);
        add(y, x, w);
    }
    dfs(1, 0);
    int u = 0, v = 0;
    for(int i = 1; i <= n; i++)
        if(dis[i] > dis[u])   u = i;
    memset(dis, 0, sizeof dis);
    dfs(u, 0);
    for(int i = 1; i <= n; i++)
        if(dis[i] > dis[v]) v = i;
    printf("%d %d %d
", u, v, dis[v]);
    return 0;
}

树的直径求解方法二

思路

以根节点出发,去寻找到这个根节点的最长路,以及次长路,得到的两个节点就是树的直径的端点。如果我们要得到直径的数值,还应该要做一次dfs,因为我们一开始选定的根节点无法保证是不是在直径上。

代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1e5 + 10;

int head[N], to[N], value[N], nex[N], cnt;
int n, ans, fir[N], sec[N], firnode[N], secnode[N];

inline ll read() {
    ll x = 1, s = 0; char c;
    c = getchar();
    while(c < ‘0‘ || c > ‘9‘) {
        if(c == ‘-‘)    x = -1;
        c = getchar();
    }
    while(c >= ‘0‘ && c <= ‘9‘) {
        s = (s << 1) + (s << 3) + (c ^ 48);
        c = getchar();
    }
    return x * s;
}

void add(int x, int y, int w) {
    to[cnt] = y;
    nex[cnt] = head[x];
    value[cnt] = w;
    head[x] = cnt++;
}

void dfs(int rt, int fa) {
    firnode[rt] = secnode[rt] = rt;
    for(int i = head[rt]; ~i; i = nex[i]) {
        if(to[i] == fa) continue;
        dfs(to[i], rt);
        if(fir[to[i]] + value[i] > fir[rt]) {//更新最长路
            sec[rt] = fir[rt];
            fir[rt] = fir[to[i]] + value[i];
            firnode[rt] = to[i];
        }
        else if(fir[to[i]] + value[i] > sec[rt]) {//更新次长路
            sec[rt] = fir[to[i]] + value[i];
            secnode[rt] = to[i];
        }
    }
}

int main() {
    n = read();
    int x, y, w;
    memset(head, -1, sizeof head);
    for(int i = 1; i < n; i++) {
        x = read(), y = read(), w = read();
        add(x, y, w);
        add(y, x, w);
    }
    dfs(1, 0);
    printf("%d %d %d", firnode[1], secnode[1], fir[1] + sec[1]);//假定我们选择的点是直径上的点。
    return 0;
}

树的重心

思路

找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。

树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离和,他们的距离和一样,则这两个点都是重心

并且,一棵树最多有两个重心,且相邻。

因此我们可以通过dfs去记录每个节点的子树节点值,然后去统计其最大结点数最小的节点。

代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1e5 + 10;

int head[N], to[N], value[N], nex[N], cnt;
int n, ans, sz[N], maxn = 0x3f3f3f3f, heart;

inline ll read() {
    ll x = 1, s = 0; char c;
    c = getchar();
    while(c < ‘0‘ || c > ‘9‘) {
        if(c == ‘-‘)    x = -1;
        c = getchar();
    }
    while(c >= ‘0‘ && c <= ‘9‘) {
        s = (s << 1) + (s << 3) + (c ^ 48);
        c = getchar();
    }
    return x * s;
}

void add(int x, int y, int w) {
    to[cnt] = y;
    nex[cnt] = head[x];
    value[cnt] = w;
    head[x] = cnt++;
}

void dfs(int rt, int fa) {
    sz[rt] = 1;
    int now_maxn = 0;
    for(int i = head[rt]; ~i; i = nex[i]) {
        if(to[i] == fa) continue;
        dfs(to[i], rt);
        sz[rt] += sz[to[i]];
        if(sz[to[i]] > now_maxn)    now_maxn = sz[to[i]];//记录最大子节点。
    }
    if(n - sz[rt] > now_maxn) now_maxn = n - sz[rt];//其父节点也算是他的子节点
    if(now_maxn < maxn) heart = rt, maxn = now_maxn;
}

int main() {
    n = read();
    int x, y, w;
    memset(head, -1, sizeof head);
    for(int i = 1; i < n; i++) {
        x = read(), y = read(), w = read();
        add(x, y, w);
        add(y, x, w);
    }
    dfs(1, 0);
    printf("%d
", heart);
    return 0;
}

以上是关于树的直径与重心的主要内容,如果未能解决你的问题,请参考以下文章

树的直径与重心

[算法模版]树的重心和直径

树的重心及直径

树讲解——树的输入,重心,直径

树的直径

bzoj3510 首都 LCT 维护子树信息+树的重心