浅谈树的直径和树的重心


title: 树-学习
date: 2019-08-02 10:03:56
tags: NULL

树的直径

定义

给定一棵树,树中每条边都有一个权值,树中两点之间的距离定义为连接两点的路径边权之和。树中最远的两个节点之间的距离被称为树的直径,连接这两点的路径被称为树的最长链。后者通常也可称为直径,即直径是一个数值概念,也可代指一条路径

性质

  1. 直径两端点一定是两个叶子节点
  2. 距离任意点最远的点一定是直径的一个端点,这个基于贪心求直径方法的正确性可以得出
  3. 对于两棵树,如果第一棵树直径两端点为(u,v),第二棵树直径两端点为(x,y),用条边将两棵树连接,那么新树的直径一定是u,v,x,y中的两个点
  4. 对于一棵树,如果在一个点的上接一个叶子节点,那么最多会改变直径的一个端点
  5. 若一棵树存在多条直径,那么这些直径交于一点且交点是这些直径的中点

    算法

    求树的直径有两种算法,一种是跑两边搜索,另一种是用树形DP去求(可惜我不会qwq)
    这里我们来介绍用两遍搜索求树的直径的方法.首先,我们从任意一个点出发,找出和ta的距离最远的点u;然后再从u出发,搜索树上距离u最远的节点v.uv之间的路径就是树的直径.
    证明:OI不需要证明……

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #define ll long long
    #define INF 0x7fffffff
    #define re register

    using namespace std;

    int read()
    {
    register int x = 0,f = 1;register char ch;
    ch = getchar();
    while(ch > '9' || ch < '0'){if(ch == '-') f = -f;ch = getchar();}
    while(ch <= '9' && ch >= '0'){x = x * 10 + ch - 48;ch = getchar();}
    return x * f;
    }

    struct edge{
    int next,to,v;
    }e[100005];

    int n,m,x,y,z,cnt,maxx,j,d[100005],vis[100005],last[100005];

    void add(int x, int y, int z)
    {
    e[++cnt].to = y;
    e[cnt].v = z;
    e[cnt].next = d[x];
    d[x] = cnt;
    }

    void dfs(int k, int l)
    {
    if(l > maxx)
    {
    maxx = l;
    j = k;
    }
    for(re int i = d[k]; i; i = e[i].next)
    if(!vis[e[i].to])
    {
    vis[e[i].to] = 1;
    last[e[i].to] = k;
    dfs(e[i].to,l + e[i].v);
    }
    }

    void work()
    {
    vis[1] = 1;
    dfs(1,0);
    memset(vis,0,sizeof(vis));
    memset(last,0,sizeof(last));
    maxx = 0;
    vis[j] = 1;
    dfs(j,0);
    printf("%d ",maxx);
    while(j != 0)
    {
    printf("%d ",j);
    j = last[j];
    }
    }

    int main()
    {
    n = read();
    m = read();
    for(re int i = 1; i <= m; i++)
    {
    x = read(); y = read(); z = read();
    add(x,y,z);
    add(y,x,z);
    }
    work();
    return 0;
    }

树的重心

定义

树的重心也叫树的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个 点后最大连通块(一定是树)的结点数最小。

性质

  1. 树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离和,他们的距离和一样。
  2. 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
  3. 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
  4. 一棵树最多有两个重心,且相邻。

    算法

    和树的最大独立问题类似,先任选一个结点作为根节点,把无根树变成有根树,然后设d(i)表示以i为根的子树的结点的个数。不难发现d(i)=∑d(j)+1,j∈s(i)。s(i)为i结点的所有儿子结点的编号的集合。程序也十分简单:只需要DFS一次,在无根树有根数的同时计算即可,连记忆化都不需要——因为本来就没有重复计算。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #define ll long long
    #define INF 0x7fffffff
    #define re register

    using namespace std;

    int read()
    {
    register int x = 0,f = 1;register char ch;
    ch = getchar();
    while(ch > '9' || ch < '0'){if(ch == '-') f = -f;ch = getchar();}
    while(ch <= '9' && ch >= '0'){x = x * 10 + ch - 48;ch = getchar();}
    return x * f;
    }

    struct edge{
    int next,to,v;
    }e[100005];

    int n,m,x,y,z,cnt,maxx,j,d[100005],vis[100005],last[100005];

    void add(int x, int y, int z)
    {
    e[++cnt].to = y;
    e[cnt].v = z;
    e[cnt].next = d[x];
    d[x] = cnt;
    }

    void dfs(int k, int l)
    {
    if(l > maxx)
    {
    maxx = l;
    j = k;
    }
    for(re int i = d[k]; i; i = e[i].next)
    if(!vis[e[i].to])
    {
    vis[e[i].to] = 1;
    last[e[i].to] = k;
    dfs(e[i].to,l + e[i].v);
    }
    }

    void work()
    {
    vis[1] = 1;
    dfs(1,0);
    memset(vis,0,sizeof(vis));
    memset(last,0,sizeof(last));
    maxx = 0;
    vis[j] = 1;
    dfs(j,0);
    printf("%d ",maxx);
    while(j != 0)
    {
    printf("%d ",j);
    j = last[j];
    }
    }

    int main()
    {
    n = read();
    m = read();
    for(re int i = 1; i <= m; i++)
    {
    x = read(); y = read(); z = read();
    add(x,y,z);
    add(y,x,z);
    }
    work();
    return 0;
    }
原文地址:https://www.cnblogs.com/aurorapolaris/p/13502517.html