树的重心总结

1.定义

如果在树中选择某个节点并删除,这棵树将分为若干棵子树,统计子树节点数并记录最大值。取遍树上所有节点,使此最大值取到最小的节点被称为整个树的重心。

(这里以及下文中的「子树」若无特殊说明都是指无根树的子树,即包括「向上」的那棵子树,并且不包括整棵树自身。)

用通俗一点的语言,就是将这个点去掉后,剩下的最大子树最小

2.性质

1.若树的重心不唯一,那么最多有两个,并且两个重心相邻

2.以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。

3.树中所有点到某个点的距离和中,到重心的距离和是最小的
   如果有两个重心,那么到它们的距离和一样

4.把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。

5.在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

3.实现方式

可以用深搜去实现,去统计一个点以此点为根节点的子树的大小,那么总结点数-当前节点总数就是剩余节点总数,然后去让最大的值最小

4.例题

P1395 会议

思路:带权树的重心板题,直接用换根dp去处理即可,跑两边dfs

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int u,v;
vector<int> e[200005];
int sz[200005];
int ans[200005];
int dp[200005];
void dfs1(int v,int fa)
{
	sz[v]=1;
	for(int u:e[v])
	{
		if(u!=fa)
		{
			dfs1(u,v);
			sz[v]+=sz[u];
		}
	}
}
void get(int v,int fa)
{
	for(int u:e[v])
	{
		if(u!=fa)
		{
			get(u,v);
			ans[v]+=ans[u]+sz[u];
		}
	}
}
void dfs2(int v,int fa)
{
	for(int u:e[v])
	{
		if(u!=fa)
		{
			dp[u]=dp[v]+(n-sz[u])-sz[u];
			dfs2(u,v);
		}
	}
}
signed main()
{
	cin>>n;
	for(int i=1;i<=n-1;i++)
	{
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(1,-1);
	get(1,-1);
	dp[1]=ans[1];
	dfs2(1,-1);
	int maxn=0x3f3f3f3f,flag=0;
	for(int i=1;i<=n;i++)
	{
		if(dp[i]<maxn)
		{
			flag=i;
			maxn=dp[i];
		}
	}
	cout<<flag<<" "<<maxn;
	return 0; 
}

 P1364 医院设置

思路:和上面那个同理 

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int w;
int u,v;
vector<int> e[200005];
int sz[200005];
int ans[200005];
int dp[200005];
int sum;
void dfs1(int v,int fa)
{
	for(int u:e[v])
	{
		if(u!=fa)
		{
			dfs1(u,v);
			sz[v]+=sz[u];
		}
	}
}
void get(int v,int fa)
{
	for(int u:e[v])
	{
		if(u!=fa)
		{
			get(u,v);
			ans[v]+=ans[u]+sz[u];
		}
	}
}
void dfs2(int v,int fa)
{
	for(int u:e[v])
	{
		if(u!=fa)
		{
			dp[u]=dp[v]+(sum-sz[u])-sz[u];
			dfs2(u,v);
		}
	}
}
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w>>u>>v;
		sum+=w;
		sz[i]=w;
		if(u!=0)
		{
			e[i].push_back(u);
		}
		if(v!=0)
		{
			e[i].push_back(v);
		}
	}
	dfs1(1,-1);
	get(1,-1);
	dp[1]=ans[1];
	dfs2(1,-1);
	int minn=0x3f3f3f3f;
	for(int i=1;i<=n;i++)
	{
		minn=min(minn,dp[i]);
	}
	cout<<minn;
	return 0;
}

B. Kay and Snowflake

 

题意:就是去求,给你q个询问,每次询问以p为根节点的重心是哪个点

思路:我们由性质四可以知道,一个点的重心,一定在这个点到子节点的重心上,那么我们可以分析两种情况,子节点的重心是不是最大的联通量,上面的更大,那么我们将子节点重心上移更优,否则就是直接就是重心

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,q;
int u,v;
vector<int> e[300005];
int sz[300005];
int mx[300005];//去统计每个点的最大子树 
int ans[300005];
int f[300005];
void dfs(int v,int fa)
{
	sz[v]=1;
	for(int u:e[v])
	{
		if(u!=fa)
		{
			dfs(u,v);
			sz[v]+=sz[u];
			mx[v]=max(mx[v],sz[u]);
		}
	}
	int maxn=mx[v];//当前最大值为当前点的最大子树
	ans[v]=v;
	for(int u:e[v])
	{
		if(u!=fa)
		{
			int zhong=ans[u];
			int shang=max(mx[zhong],sz[v]-sz[zhong]);
			int k=zhong;
			while(f[zhong]!=v)
			{
				zhong=f[zhong];//网上跳一步
				int z=max(mx[zhong],sz[v]-sz[zhong]);
				if(z<shang)
				{
					shang=z;
					k=zhong;
				} 
				else
				{
					break;
				}
			}
			if(maxn>shang)
			{
				maxn=shang;
				ans[v]=k;
			}
		}
	 } 
}

signed main()
{
	cin>>n>>q;
	for(int i=2;i<=n;i++)
	{
		cin>>u;
		f[i]=u;
		e[u].push_back(i);
		e[i].push_back(u);
	}
	dfs(1,-1);
	for(int i=1;i<=q;i++)
	{
		cin>>v;
		cout<<ans[v]<<"\n";
	}
	return 0;
}

### 关于AtCoder平台上的树结构计数问题 #### 使用双重计数方法解决树结构问题 在处理树结构的计数问题时,可以采用双重计数的方法。这种方法的核心是从叶子节点出发构建满足特定条件的树形结构。具体来说,在每一步操作中都保持唯一的构造方式,从而确保最终形成的树是独一无二的[^2]。 ```python def count_trees_with_double_counting(degrees): """ 计算具有给定度序列的有向有标号树的数量 :param degrees: 列表形式表示各个节点的入度 :return: 符合条件的不同树的数量 """ from math import factorial as fact n = len(degrees) # 初始化组合数数组C[n][k], 表示从n个不同元素中取出k个元素的方案总数 C = [[0]*(n+1) for _ in range(n+1)] for i in range(n+1): C[i][0]=1 for j in range(1,min(i,n)+1): C[i][j]=(C[i-1][j]+C[i-1][j-1]) result = 1 total_degrees_sum = sum(degrees)-len(degrees)+1 for deg in set(degrees): cnt = degrees.count(deg) result *= pow(C[total_degrees_sum][cnt], -1, int(1e9+7)) * fact(cnt) return result % int(1e9+7) ``` 此函数实现了基于双重计数原理来统计符合条件的树数量的功能。需要注意的是,这里假设输入是一个有效的度序列,并且已经转换成了对应的入度列表`degrees`。 #### 寻找特殊性质以简化计算过程 另一种有效策略是在整棵树内识别最特别的关键点——比如重心或叶节点。这些关键位置可以帮助推导出整个树的具体形状。例如,当尝试确定某个节点作为父级的可能性时,可以通过比较其子树规模来进行判断;而在验证根节点的有效性方面,则需进一步确认全局属性的一致性[^3]。 #### 应用于实际竞赛题目中的技巧总结 针对像ARC101E这样的具体实例,解决方案通常涉及巧妙利用图论概念以及数据结构特性。上述提到的技术手段能够帮助参赛者快速找到突破口并设计高效的算法实现: - **LCA的应用**:对于某些类型的查询,最近公共祖先(Lowest Common Ancestor,LCA)提供了重要的辅助作用; - **自底向上动态规划**:从底层开始逐步累积信息直至顶层,有助于减少重复工作量; - **贪心选择法则**:每当面临多条路径可供挑选的情况之下,优先选取那些能来更大收益的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值