树的重心

关于树的重心

Define

一棵具有n个节点的无向树,若以某个节点为整棵树的根,他的每个儿子节点的大小都>=n/2 ,则这个节点即为该树的重心

性质

  1. 删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心
  2. 树中所有节点到重心的距离之和最小,如果有两个重心,那么他们距离之和相等
  3. 两个树通过一条边合并,新的重心在原树两个重心的路径上
  4. 树删除或添加一个叶子节点,重心最多只移动一条边

解法

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;
}
posted @ 2020-12-01 10:44  雾隐  阅读(174)  评论(1编辑  收藏  举报