2019 徐州 M. Kill the tree(树的重心的性质,子树合并)
这题写得好爽
题意
求一棵有根树中,以每个节点为根的子树的所有重心
思路
前置芝士
树的重心
性质:
1. 最大的子树最小
2. 找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡
3. 树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离和,他们的距离和一样,则这两个点都是重心(即重心可以有两个)
4. 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上
5. 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置
6. 一棵树最多有两个重心,且相邻。
本题:
什么条件下重心可以往上爬: 设当前重心为u,根节点为rt,sz[u] 是 u 结点的为根节点的子树的结点个数。若u往上爬,u的所有子节点到u的距离会加一,其它点到u的距离会减一,根据题目给的计算式,这个该变量必须是负值才可以往上爬:那么得到一个条件是:sz[rt] - sz[u] > sz[u],满足这个条件 u 就可以向上爬,直到 u == rt 或者 条件不满足
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=4e5+5;
int n,dep[maxn],fa[maxn],size[maxn],son[maxn],ans[maxn];
vector<int>gap[maxn];
void dfs(int now,int pre)
{
dep[now] = dep[pre]+1;
fa[now] = pre;
size[now] = 1;
son[now] = 0;
for(auto &x:gap[now]){
if(x==pre)continue;
dfs(x,now);
size[now] += size[x];
if(size[x] > size[son[now]])son[now] = x;
}
}
void solve(int now,int pre){
for(int i=0;i<gap[now].size();i++){
if(gap[now][i]!=pre)solve(gap[now][i],now);
}
ans[now]=now;
if(!son[now])return;
int x=ans[son[now]];
while(x!=fa[now]&&size[now]-size[x]>size[x])x=fa[x];
ans[now]=x;
}
int main(){
scanf("%d",&n);
for(int i=1,x,y;i<n;i++){
scanf("%d%d",&x,&y);
gap[y].push_back(x);
gap[x].push_back(y);
}
dfs(1,0);
solve(1,0);
for(int i=1;i<=n;i++){
int t=ans[i],f=fa[t];
if(f&&size[i]-size[t]==size[t]){
if(f<t)swap(t,f);
printf("%d %d\n",t,f);
}
else printf("%d\n",t);
}
}