Catalog
  1. 1. 树的直径
    1. 1.1. 定义
    2. 1.2. 性质
    3. 1.3. 算法
    4. 1.4. 代码
  2. 2. 树的重心
    1. 2.1. 定义
    2. 2.2. 性质
    3. 2.3. 算法
    4. 2.4. 代码
树-学习

树的直径

定义

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

性质

  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\n",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\n",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;
    }
Author: wflight
Link: http://yoursite.com/2019/08/02/树-学习/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付寶

Comment