AcWing 846. 树的重心

题目描述

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

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

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

输入格式

第一行包含整数 nn,表示树的结点数。

接下来 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

dfs算法求解

分析

首先使用数组模拟链表建立无向图,注意,因为是无向图,所以边的数目应该开成点数目的两倍

然后使用dfs遍历图,dfs(u)返回的是以u为根节点的子树中节点的数目(包括u本身)

使用sum表示以u为根的子树的大小,使用size表示每个节点u删除后,剩余联通块中节点数目的最大值,则分两部分看

  1. 对于u的所有孩子节点(在u的列表中,并且还没有被访问过的节点)j:用对j进行dfs(j)得到该子树的大小s,遍历所有节点求出 size = max(s)
  2. 对u的父节点所在的连通块:其大小为 n - sum

所以 size = max(size, n-sum)

每次遍历所有u的孩子节点结束后,得到删除节点u之后,剩余连通块中节点数目的最大值:size

那么只需要维护一个全局变量ans,求得所有节点size的最小值 : ans = min(ans, size)

如图,从1开始遍历,当u=4这个节点的时候

其孩子子树大小分别为:2(3-9), 1(6)

其父节点所在的子树大小为:n-sum = 9 - 4 = 5 (1-2-8-5-7)

所以删除u之后剩余连通块中点的数目的最大值为:size = 5

遍历所有点的size就可得到最后的结果

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 100010;
// 存图
int h[N], e[2*N], ne[2*N], idx = 0;
bool state[N]; // 标记每个点是否被访问过 
int n;
int ans = N; // 最后的结果 

// 建边
void add(int a, int b)
{
	e[idx] = b;
	ne[idx] = h[a]; 
	h[a] = idx++;
}

// 返回以u为根的子树的大小,包含u 
int dfs(int u)
{
	state[u] = true; //访问了该点 

	// sum 表示以u为根的子树的大小 
	// size 表示以u为根的所有子树中节点数目的最大值 
	int sum  = 1, size = 1;
	// 枚举u所有的子树
	for(int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i]; 
		// 如果该子节点没有被访问过
		if(!state[j])
		{
		  //  state[j] = true;
			int s = dfs(j); // 该子树的大小
            // state[j] = false;
    
			sum += s;
			size = max(size, s);
		}
		 
	} 
	size = max(size, n - sum); //再看一眼u的父节点所在的子树大小n-sum 
	
	ans = min(ans, size);
	
	return sum;
}


int main()
{
	memset(h, -1, sizeof h);
	
	scanf("%d", &n);
	for(int i = 0; i < n-1; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
		add(b, a);
	}
	
	
// 	state[1] = true;
	dfs(1);
	
	cout << ans << endl;
	return 0;
}



时间复杂度

\(O(m+n)\)

参考文章

https://www.acwing.com/solution/content/4917/

posted @ 2022-02-27 09:53  VanHope  阅读(43)  评论(0编辑  收藏  举报