一.树的重心定义      

树的重心:找到一个点,其所有的子树中最大的子树节点数最少.


二.树的重心性质

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

     

三.如何求树的重心

找到一个点,其所有的子树中最大的子树节点数最少.

 根据这个定义,我们跑一遍DFS即可,注释在代码里

ll minl=INF,ans;
/*
sz数组表示以当前节点为根,向下的子树的节点数目
dp数组表示以当前节点为根,子树中最大节点数目是多少
*/
void dfs(int u,int fa)
{
    sz[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)//链式前向星遍历
    {
        int e=edge[i].e;
        if(e!=fa)//无向图,不加这个会成环
        {
            dfs(e,u);
            sz[u]+=sz[e];//回溯加一下
            dp[u]=max(dp[u],sz[e]);//每次遍历完一颗子树都比较

        }
    }
    dp[u]=max(dp[u],n-sz[u]);//不要忽略向上的节点个数,比较完之后便是当前节点的最大子树节点
    if(minl>dp[u])
    {
        minl=dp[u];
        ans=u;
    }
}

四.树的重心例题

洛谷P1395:

  题目描述

  有一个村庄居住着n个村民,有n-1条路径使得这n个村民的家联通,每条路径的长度都为1。现在村长希望在某个村民家中召开      一场会议,村长希望所有村民到会议地点的距离之和最小,那么村长应该要把会议地点设置在哪个村民的家中,并且这个距离     总和最小是多少?若有多个节点都满足条件,则选择节点编号最小的那个点。

 输入格式

 第一行。一个数n,表示有n个村民。

 接下来n-1行,每行两个数字a和b,表示村民a的家和村民b的家之间存在一条路径。

 输出格式

 一行输出两个数字x和y

 x表示村长将会在哪个村民家中举办会议

 y表示距离之和的最小值

 中文题意

 题目思路:这已经非常简单啦,直接把模板套进去找到重心,编号最小在过程中就找到,然后以重心为起点跑一遍最短路即可:

#include <bits/stdc++.h>
#define E 2.718
using namespace std;
typedef  long long ll;
const ll INF=0x7f7f7f7f;
const int maxn=1e6+8;
const double eps=1e-10;
const ll mod=1000000007;
ll n,m,f;
ll sz[maxn];// 节点数大小
ll dp[maxn];// 保留最大子数节点数
ll dis[maxn];
bool vis[maxn];
int head[maxn];
struct node{
    ll e,next;
}edge[maxn];
ll cnt=0;
void addedge(ll u,ll v)
{
    edge[cnt]=node{v,head[u]};
    head[u]=cnt++;
    edge[cnt]=node{u,head[v]};
    head[v]=cnt++;
}
ll minl=INF,ans;
void dfs(int u,int fa)
{
    sz[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int e=edge[i].e;
        if(e!=fa)
        {
            dfs(e,u);
            sz[u]+=sz[e];
            dp[u]=max(dp[u],sz[e]);

        }
    }
    dp[u]=max(dp[u],n-sz[u]);
    if(minl>dp[u]||minl==dp[u]&&ans>u)
    {
        minl=dp[u];
        ans=u;
    }
}
void spfa()
{
    queue<int>q;for(int i=1;i<=n;i++) dis[i]=INF,vis[i]=false;
    q.push(ans);vis[ans]=true;dis[ans]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();vis[u]=false;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int e=edge[i].e;
            if(dis[e]>dis[u]+1)
            {
                dis[e]=dis[u]+1;
                if(!vis[e])
                {
                    vis[e]=true;
                    q.push(e);
                }
            }
        }
    }
}
int main()
{
    memset(head,-1,sizeof(head));
    scanf("%lld",&n);
    for(int i=1;i<=n-1;i++)
    {
        ll x,y;scanf("%lld%lld",&x,&y);
        addedge(x,y);
    }
    dfs(1,0);
    spfa();
    ll sum=0;
    for(int i=1;i<=n;i++)
        sum+=dis[i];
    printf("%lld %lld\n",ans,sum);
    return 0;
}

 


五.总结   

了解树的重心的性质,直到其求法,反正有好处.尤其需要注意 例题中的性质,树的重心到树上各点的距离之和最小,但有前提,边的权值都是一样的.