DFS-树的重心题解

一、题目描述

给定一颗树,树中包含 n 个结点(编号 1∼n )和 n−1 条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式:
第一行包含整数 n ,表示树的结点数。

接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。

输出格式:
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围:
1 ≤ n ≤ 1 0 5 1\leq n\leq 10^{5} 1n105

输入样例:

9
1 2
1 4
1 5
2 7
2 8
4 9
5 3
5 6

输出样例:

3

二、题目分析

题目要求我们找到树的重心,输出删除这个重心后,剩余各个连通块中,结点数的最大值。
因此,我们可以通过遍历树中的每一个结点,求出删除该节点后,各个连通块中的结点数。
我们以输入样例中的树(下图所示)作为例子进行分析。
在这里插入图片描述

删除结点5后,形成三个连通块,如下图:
在这里插入图片描述
删除结点1后,形成三个连通块,如下图:
在这里插入图片描述
我们可以发现,删除某个结点后剩余的连通块数量,实际上等于与该结点相连的结点数。同时,连通块1、2、3也可以视作所删除结点的子树。
因此,树的重心问题,可以转化为求子树的结点个数问题。我们可以用DFS(深度优先搜索)来求解。

那么我们具体应该如何求解?

1.计算num值
在一棵树中,每一个结点都可以作为根节点。在本例中,我们以1作为根结点,于是问题转化为:以1为根结点的情况下,求出每个结点所有子树的结点个数加上自身,我们记为num。即num=子树1结点个数+子树2结点个数+...+1
下图的红色数字表示每个结点的num值。
在这里插入图片描述
2.遍历结点,计算剩余连通块的结点数
为了方便表述,我们将待删除的结点记为i,与i连接的结点记为j,树的总结点个数记为n
由于删除结点i后剩余的连通块数量,等于与i相连的结点数。因此我们可以分两种情况进行讨论:

  • ji的子节点,则j所在连通块的结点数量为num[j];
  • ji的父结点,则j所在连通块的结点数量为n-num[i];

遍历所有结点,得到的结果如下表格所示:

在这里插入图片描述

三、代码示例

#include<iostream>
#include<cstring>
using namespace std;
const int N=100010;
int n;
bool state[N];                //标记当前结点是否被遍历过 
int num[N];                   //结点i及其它所有子树的结点个数 

//链表相关变量的定义
int h[N],e[N*2],ne[N*2],idx;  //分别表示头结点、结点的值、结点指向的下一个结点,idx不知道

void add(int a,int b){        //新增一个结点,将其放到头结点h[a]的后边,该结点的值为b, 
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx;
	idx++;                    //idx是新增的结点下标 
}

//计算num值(结点u的所有子树结点个数+1) 
void dfs(int u){  
	state[u]=true;       //当前结点已经走过啦
	num[u]=1;            //u结点自身 
	
	//如果u为叶子结点,则下边的循环不会进行 
	for(int i=h[u];i!=-1;i=ne[i]){ //遍历与结点u相连的所有结点 
		int id=e[i];               //当前遍历的结点编号为id 
		if(!state[id]){            //如果结点id没有遍历过
			dfs(id);               //计算结点id的num值 
			num[u]+=num[id];       //累加所有子树的结点个数			
		}
	} 
}
int solve(){
	int ans=N;              //最终的答案 
	for(int i=1;i<=n;i++){  //遍历所有结点,将待删除的结点记为i 
		int res=0;
		//遍历与结点i相连的所有结点j,得到删除每个结点i后,连通图中结点数的最大值res 
		for(int k=h[i];k!=-1;k=ne[k]){ //注意,头结点是一个指针 
			int j=e[k];                //j为当前结点的编号 
			if(num[j]<num[i])          //j是i的子节点时 
				res=max(res,num[j]);
			if(num[j]>num[i])
				res=max(res,n-num[i]); //j是i的父节点时 
		} 
		ans=min(ans,res);
	}
	return ans;
}
int main(){
	cin>>n;
	memset(h,-1,sizeof h); //初始化链表 
	for(int i=0;i<n-1;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		add(a,b),add(b,a);
	}
	dfs(1);   //从结点1开始遍历 
	cout<<solve();      //输出结果 
	return 0;
} 

代码中用链表存储树。出于懒惰,就不写该数据结构在使用过程中的亿些细节了。

  • 34
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值