树上问题:基础版T1:Kay and Snowflake

树上问题种类非常多,今天的主要讲述的是以搜索为框架,加一些树上优化的题目。

先定义一些通用的符号:size[i]:以i号结点为根的子树的结点个数。dist[i]:i号结点到根节点的距离(无边权情况下默认为一)
p[i][j]:只有最后一题出现了,用于树上倍增,表示第i个点向上的第2^j点。dep[i]一个点i的深度(也就是无边权情况下下的dist)

我们来看第一道题::
题目链接:https://www.luogu.org/problem/CF685BKay and Snowflake
题目大意:给你一棵树,问你每颗子树的重心。

首先关于重心:定义:一棵树将某个点去掉,会把这棵树分为好几块,以最大的一块的结点个数作为max_size,这样的话每一个点都对应着一个max_size,这些值当中最小的那个所对应的结点即为树的重心。

定义:son[i]表示以i为根的子树中size最大的子树
性质:如果一棵树的某个子树的size*2>size[root]那么这个顶点是这些子树中size最大的子树的根
重点:若x为根节点,这颗子树的重心一定在根节点(x)上或者是son[x]为根的子树上。
一个树的重心的dep一定小于等于它的子树的重心
这就有了我们的核心代码:

	if(size[son[x]]*2>size[x]){//如果这个儿子的结点数超过整个的两倍 
		k=son[x];//找到第一个没超过两倍的子树根节点 
		while((size[x]-size[k])*2>size[x])k=fa[k];
		ans[x]=k;//以它为答案 
	}
	else{
		ans[x]=x;
	}

以上就是寻找重心的过程,加上深度优先搜索就好了。

下附AC代码:

#include<iostream>
#include<cstring>
using namespace std;
struct edge{
	int to,next;
}e[300010];//邻接表建图
int eid=0;
int head[300010];
int ans[300010];
void insert(int u,int v){
	eid++;
	e[eid].to=v;
	e[eid].next=head[u];
	head[u]=eid;
}
int fa[300010],son[300010],size[300010];//父亲结点
int n,m;
void dfs(int x){//普通的dfs
	size[x]=1;
	for(int i=head[x];i+1;i=e[i].next){
		int k=e[i].to;
		dfs(k);
		size[x]+=size[k];
		if(size[k]>size[son[x]])son[x]=k;
	}
	int k;
	if(size[son[x]]*2>size[x]){//如果这个儿子的结点数超过整个的两倍 
		k=son[x];//找到第一个没超过两倍的子树根节点 
		while((size[x]-size[k])*2>size[x])k=fa[k];
		ans[x]=k;//以它为答案 
	}
	else{
		ans[x]=x;
	}
	return ;
}
int main(){
	memset(head,-1,sizeof(head));
	cin>>n>>m;
	for(int i=2;i<=n;i++){
		cin>>fa[i];
		insert(fa[i],i);
	}
	dfs(1);
	for(int i=1;i<=m;i++){
		int x;
		cin>>x;
		cout<<ans[x]<<endl;
	}
}

一些说明:下文中我的dfs一般会有两个形参,dfs(int x,int fa)分别代表结点与其父节点,两种均可但是下文的更加通用,因为上文中我们并不需要沿着边向上查找,所以说我是建了一个有向树,只能从父亲访问儿子,因此我就不需要在枚举边的时候判断我要去的点是否是父亲,是的话还要跳过。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值