一.树的重心定义
树的重心:找到一个点,其所有的子树中最大的子树节点数最少.
二.树的重心性质
- 树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离和,他们的距离和一样。
- 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
- 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
- 一棵树最多有两个重心,且相邻。
三.如何求树的重心
找到一个点,其所有的子树中最大的子树节点数最少.
根据这个定义,我们跑一遍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;
}
五.总结
了解树的重心的性质,直到其求法,反正有好处.尤其需要注意 例题中的性质,树的重心到树上各点的距离之和最小,但有前提,边的权值都是一样的.