2022-11-23 Acwing每日一题

本系列所有题目均为Acwing课的内容,发表博客既是为了学习总结,加深自己的印象,同时也是为了以后回过头来看时,不会感叹虚度光阴罢了,因此如果出现错误,欢迎大家能够指出错误,我会认真改正的。同时也希望文章能够让你有所收获,与君共勉!

树的重心

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

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

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

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

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

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

数据范围
1≤n≤105
输入样例

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

输出样例:

4

算法原理

基础知识

这里先介绍图和树可以用什么来存储(树可以看成是一种无向无环图,所以存储方式一样)。我们可以使用邻接矩阵和邻接表来存储,其中邻接矩阵适用于稠密图,邻接表适用于稀疏图和树。
再来看看题目中概念——重心,每个节点的连通块中最大点数中的最小值,这里解释一下连通块,如果在图中删除某个节点,那么剩下及格互不相关的部分就称为连通块(每一部分都是内部连通的),那么这个连通块中点的数量被称为连通块的点数,那么每个节点的连通块的最大点数就是指连通块中点数最大的可以称为这个节点的最大连通块点数,那么整个图中的结点的最大连通块点数中最小的就是树的重心,重心可以有多个(树有子树),但整个树只有一个。

寻找最大的连通块点数

image
如y总的图所示,假设我们需要求结点值4的最大连通块的点数,我们可以将这棵树分为三部分连通块,一个是以节点值3开始的,一个是单独的节点值6,另一部分就是4以上的整个部分。对于前两部分,我们可以通过树和图的遍历方法中的DFS(深度优先遍历)去获得子树的点数,对于另外一部分点数我们可以发现这不就是整个图的结点数n减去从4开始往下的这棵树的点数sum嘛,因此我们就可以得到这个节点的最大连通块点数,因此我们可以从图的遍历过程中(还是统一用图来表示树吧)找到每一个节点的最大连通块点数,然后找出其中的最小值即可。

还有一些补充在代码里哦。

代码实现

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 100010,M = N * 2;	// 双向边所以要乘2
int h[N],e[M],ne[M],idx;
/* 邻接表的标配,其实这里的邻接表就是拉链法中的哈希表,用一个链表存储,用h[i]来存储第i个结点通向某个结点,
就把这个结点头插入第i个结点的链表里,因此这个链表就表示编号为i的结点指向的哪些节点,需要注意的是树是无向图,所以a能到b
所以b也能到a,再插入的时候要双向插入,即a能到b,b也要能到a。
*/
bool st[N];	// 表示结点的状态,如果为true说明搜索过,否则为没搜索过
int ans = N;	// 整张图的最大连通块中最小的点数
int n;

void add(int a,int b){	// a指向b,即把b插入到h[a]中
	e[idx] = b,ne[idx] = h[a], h[a] = idx++;
}

int dfs(int x){
	st[x] = true;	// 表示x被搜索过a
	int size = 0 ,sum = 1;	// size表示这个节点的最大连通块点数,sum表示x结点整棵树的点数,所以初始就有1,这样返回这个子树的点数时就至少有一个
	for(int i=h[x] ; i != -1 ; i = ne[i]){	// 对于每一个子树都需要寻找点数并求和且比较与size谁更大,更新size
		int j = e[i];
		if(!st[j]){
			int s = dfs(j);	// 寻找编号为x的子树的点数
			size = max(s,size);	// 比较每一次这个节点的连通点数与当前子树的个数比较进行更新,后面还有与x以上的连通块比较
			sum += s;	// 求出子树的总节点数,为了求x以上的连通块的点数
		}	
	}
	// 更新size
	size = max(size,n-sum);	// 因为最开始sum有1,所以这里不用-1,只需要更新size
	ans = min(ans,size);	// 比较ans与当前节点的最大连通块点数谁更小,这样遍历完整个树就可以得到最小值,时间复杂度为O(n+m)
	return sum;	// 将当前子树的点数返回给上一个结点
}

int main(){
	cin >> n;
	memset(h,-1, sizeof h);
	for(int i=0 ; i< n ; ++i){
		int a,b;
		cin >> a  >> b ;
		add(a,b),add(b,a);
	}

	dfs(1);	// 这里从任意编号开始搜都可以,毕竟这里用的是图的遍历方法DFS,只要不重复,就一条路走到底,并不是前中后序遍历
	cout << ans << endl;
	return 0;
}

posted @ 2022-11-23 23:03  ZmQmZa  阅读(22)  评论(0编辑  收藏  举报