树的重心
关于树的重心
Define
一棵具有n个节点的无向树,若以某个节点为整棵树的根,他的每个儿子节点的大小都>=n/2 ,则这个节点即为该树的重心
性质
- 删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心
- 树中所有节点到重心的距离之和最小,如果有两个重心,那么他们距离之和相等
- 两个树通过一条边合并,新的重心在原树两个重心的路径上
- 树删除或添加一个叶子节点,重心最多只移动一条边
解法
1.dfs求解
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#define ll long long
const ll maxn=1e5+10;
ll n,S,tot,ans,sum=maxn*maxn;
ll head[maxn*2],vis[maxn],dep[maxn],siz[maxn];
struct node
{
ll u,v,w,nxt;
} s[maxn*2];
inline void add(ll u,ll v)
{
s[++tot].v=v;
s[tot].nxt=head[u];
head[u]=tot;
}
inline void dfs(ll x)
{
vis[x]=1;
siz[x]=1;
ll maxx=-1;
for(int i=head[x];i;i=s[i].nxt)
{
ll y=s[i].v;
if(vis[y]) continue;
dfs(y);
siz[x]+=siz[y];
maxx=std::max(maxx,siz[y]);
}
maxx=std::max(maxx,n-siz[x]);
if(sum>maxx)
{
sum=maxx;
ans=x;
}
}
int main(void)
{
scanf("%lld %lld",&n,&S);
for(int i=1;i<=n-1;i++)
{
ll x,y;
scanf("%lld %lld",&x,&y);
add(x,y);
add(y,x);
}
dfs(S);
printf("%lld %lld",ans,sum);
}
2.朴素用 \(O(n^2)\) 的暴力求解多个重心编号
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#define ll long long
const ll maxn=1e3+10;
ll n,S,tot;
ll fa[maxn],head[maxn*2],siz[maxn];
struct node
{
ll u,v,nxt;
}s[maxn*2];
inline void add(ll u,ll v)
{
s[++tot].v=v;
s[tot].nxt=head[u];
head[u]=tot;
}
inline void dfs(ll x)
{
siz[x]=1;
for(int i=head[x];i;i=s[i].nxt)
{
ll y=s[i].v;
if(y==fa[x]) continue;
fa[y]=x;
dfs(y);
siz[x]+=siz[y];
}
}
inline ll judge(int x)
{
if(n-siz[x]>(n/2))//应用性质 1 判断
{
return 0;
}
for(int i=head[x];i;i=s[i].nxt)
{
ll y=s[i].v;
if(fa[y]==x&&siz[y]>(n/2)) //同上
{
return 0;
}
}
return 1;
}
int main(void)
{
scanf("%lld %lld",&n,&S);
for(int i=1;i<=n-1;i++)
{
ll x,y;
scanf("%lld %lld",&x,&y);
add(x,y);
add(y,x);
}
dfs(S);
for(int i=1;i<=n;i++)
{
if(judge(i))
{
printf("%d ",i);
}
}
return 0;
}
3.求解一棵树的所有子树的重心
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#define ll long long
const ll maxn=1e3+10;
ll n,S,tot;
ll fa[maxn],head[maxn*2],siz[maxn],zx[maxn],an[maxn];
struct node
{
ll u,v,nxt;
}s[maxn*2];
inline void add(ll u,ll v)
{
s[++tot].v=v;
s[tot].nxt=head[u];
head[u]=tot;
}
inline void dfs(ll x)//求以 S 为根的树的各个子树的大小
{
siz[x]=1;
for(int i=head[x];i;i=s[i].nxt)
{
ll y=s[i].v;
if(y==fa[x]) continue;
fa[y]=x;
dfs(y);
siz[x]+=siz[y];
}
}
inline ll judge(int x,ll y)//判断 x 是否为 y 的重心
{
if(siz[y]-siz[x]>(siz[y]/2))
{
return 0;
}
for(int i=head[x];i;i=s[i].nxt)
{
ll v=s[i].v;
if(fa[v]==x&&siz[v]>(siz[y]/2))
{
return 0;
}
}
return 1;
}
inline void get(ll x) //求每个子树的重心
{
ll p=-1;
for(int i=head[x];i;i=s[i].nxt)
{
ll y=s[i].v;
if(y!=fa[x])
{
get(y);
if(siz[y]>siz[x]/2) p=y;//可能的重心
}
}
if(p==-1) zx[x]=x;
else zx[x]=zx[p];
while(!judge(zx[x],x)) // 从子树的重心范围内寻找重心
{
zx[x]=fa[zx[x]];
}
ll q=zx[x];
for(int i=head[q];i;i=s[i].nxt) //应用性质 3 枚举求解另一重心
{
ll v=s[i].v;
if(judge(v,x))
{
an[x]=v;
break;
}
}
}
int main(void)
{
scanf("%lld %lld",&n,&S);
for(int i=1;i<=n-1;i++)
{
ll x,y;
scanf("%lld %lld",&x,&y);
add(x,y);
add(y,x);
}
dfs(S);
get(S);
for(int i=1;i<=n;i++)
{
printf("%d:",i);
if(an[i])
{
printf("%d %d",std::min(zx[i],an[i]),std::max(zx[i],an[i]));
}
else printf("%d",zx[i]);
putchar(10);
}
return 0;
}