树上问题种类非常多,今天的主要讲述的是以搜索为框架,加一些树上优化的题目。
先定义一些通用的符号:size[i]:以i号结点为根的子树的结点个数。dist[i]:i号结点到根节点的距离(无边权情况下默认为一)
p[i][j]:只有最后一题出现了,用于树上倍增,表示第i个点向上的第2^j点。dep[i]一个点i的深度(也就是无边权情况下下的dist)
我们来看第一道题::
题目链接:https://www.luogu.org/problem/CF685B :Kay 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)分别代表结点与其父节点,两种均可但是下文的更加通用,因为上文中我们并不需要沿着边向上查找,所以说我是建了一个有向树,只能从父亲访问儿子,因此我就不需要在枚举边的时候判断我要去的点是否是父亲,是的话还要跳过。