树的重心定义(另一种):以这个点为根,那么所有的子树(不算整个树自身)的大小都不超过整个树大小的一半。
树的重心的性质:
性质 1 :树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离
和,他们的距离和一样。
性质 2 :把两棵树通过某一点相连得到一颗新的树,新的树的重心必然在连接原来两棵
树重心的路径上。
性质 3 :一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
这道题如果询问次数少的话可以直接利用树的重心模版来做,不过看成双向边,不
往指的根节点上面走就是了。但是这个询问30W,模版来就是n方的复杂度肯定不行
了。
这时就可以利用树的重心的第二个性质,对于我们从点1开始往下进行遍历,返回
的时候就可以可以看成一颗树与另一颗树相连求重心。然后就是这个性质2了。由于这
颗树的特殊性,有子树的坐标,一只沿着父亲跑到当前结点找到重心就行了。
CODE
#include <bits/stdc++.h>
using namespace std;
const int N = 300000+10;
int n,q;
vector<int> G[N]; ///存图
int ans[N]; ///答案
int son[N]; ///包括自身在内有多少子树结点
int fa[N]; ///输入用,同时代表这个点的父亲
void dfs(int u){
ans[u] = u;
son[u] = 1;
for(int i = 0;i < G[u].size();i++){
int v = G[u][i];
dfs(v);
son[u] += son[v];
}
for(int i = 0;i < G[u].size();i++) ///重心存在于子树中,记录子树重心的位置
if(son[G[u][i]]*2 > son[u]) ///*2是利用重心的性质
ans[u] = ans[G[u][i]];
while((son[u]-son[ans[u]])*2 > son[u]) ///重新连接后的重心一定在子树重心和当前结点的路径上
ans[u] = fa[ans[u]];
}
int main(void)
{
scanf("%d%d",&n,&q);
for(int i = 2;i <= n;i++){
scanf("%d",&fa[i]);
G[fa[i]].push_back(i);
}
dfs(1);
for(int i = 1;i <= q;i++){
int qq;
scanf("%d",&qq);
printf("%d\n",ans[qq]);
}
return 0;
}