树的重心

先看题:P1395会议

树的重心,就是在一棵树中拆掉一个点,把这棵树分成几个部分,使得最大的部分最小,亦即拆得均匀,这个拆掉的点就是树的重心。

那么怎么求呢?我们可以从根结点开始dfs(什么?你说是无根树。那就定义节点1为根呗),返回值是这棵子树的大小。然后定义一个\(mx\)\(mx=max\{dfs(\text{该结点的子结点的})\}\) 。最后,在枚举完子节点后,再判断他父亲那一块的大小。然后记录值。
现在唯一的问题就是父亲那块怎么解决呢?没错,用总结点数减去这棵子树的大小,就是他父亲那一块的大小。

现在来解决第二问 ,这问其实可以在解决树的重心后直接dfs或bfs,然后就可以得出总的路程了。这一块就不用详细解释了。

上代码:

#include<bits/stdc++.h>
using namespace std;
int n,ans=50005,sum=50005,road;//n是总结点数,ans是树的重心的编号,sum是其最那块的大小,road是总路程
int t[50005];//ti表示结点i到ans的距离。
queue<int>q;//q用来后面bfs用
struct graph
{
	int tot;
	int dt[100005],nxt[100005];
	int hd[50005];
	void add(int x,int y)
	{
		tot++;
		nxt[tot]=hd[x];
		hd[x]=tot;
		dt[tot]=y;
	}
}g;//graph是链式前向星。
int dfs(int x,int fa)
{
	int k=0;//k代表其所以子树的大小
	int mx=0;//mx是将这个点拆掉后最大那块的大小
	for(int i=g.hd[x];i;i=g.nxt[i])//遍历所以点子节点
	 if(g.dt[i]!=fa)//注意判该节点是否是x的父亲,不判会爆栈的
	 {
	 	int xx=dfs(g.dt[i],x);//记录这棵子树的大小
		k+=xx;//加上这棵子树的大小
		mx=max(mx,xx);//取最大值
	 }
	mx=max(mx,n-k-1);//最后判一下父亲那块,最后的-1是因为最开始没有把自己算进去
	if(mx<sum||(mx==sum&&x<ans)) sum=mx,ans=x;//如果比当前方案更优,就取这个方案
	return k+1;//返回整棵子树的大小
}
void bfs()
{
	q.push(ans);//记录最初的点
	while(!q.empty())//如果队列不为空
	{
		int xx=q.front();//记录队首
		q.pop();//弹出队首
		for(int i=g.hd[xx];i;i=g.nxt[i])//遍历链接它的节点
		{
			int yy=g.dt[i];
			if(t[yy]||yy==ans) continue;//如果这个点已经被遍历过,continue
			t[yy]=t[xx]+1;//记录距离
			road+=t[yy];//总距离要加上这个点的距离
			q.push(yy);//放进队列
		}
	}
	return ;
}
int main()
{
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int from,to;
		cin>>from>>to;
		g.add(from,to);
		g.add(to,from);
	}//输入+存图,注意是双向边
	dfs(1,0);//dfs,把1的父亲设为0,就是没有父节点了(因为没有节点0)
	bfs();//求出树的重心后,求总路程
	cout<<ans<<' '<<road;
	return 0;
}
posted @ 2020-08-18 11:42  Mine_King  阅读(225)  评论(0编辑  收藏  举报