题意
求一棵有根树中,以每个节点为根的子树的所有重心
思路
根据性质:
- 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
- 一棵树最多有两个重心,且相邻。
- 重心一定在以重儿子为根的子树的重心到该点的路径上。
我们在以重儿子为根的子树的重心到该点的路径上找答案即可。在从重儿子的重心向上爬的过程中,什么样的点是更优的呢?我们可以考虑向上走的那条边对总距离的贡献,减少的量是除了重儿子重心的子树外其他的点的数量,增加的量是重儿子重心的子树中节点的数量。也就是 s z [ r t ] − s z [ u ] > s z [ u ] sz[rt] - sz[u] > sz[u] sz[rt]−sz[u]>sz[u]时,向上爬更优。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 200010;
int n;
int h[N], e[2 * N], ne[2 * N], idx;
int sz[N], fa[N], son[N], deep[N];
int dp[N];
vector<int> ans[N];
void add(int a,int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs1(int x)
{
sz[x] = 1;
for(int i = h[x]; ~i; i = ne[i]) {
int j = e[i];
if(j == fa[x]) continue;
fa[j] = x;
deep[j] = deep[x] + 1;
dfs1(j);
sz[x] += sz[j];
if(sz[j] > sz[son[x]]) son[x] = j;
}
}
void dfs2(int x)
{
for(int i = h[x]; ~i; i = ne[i]) {
int j = e[i];
if(j == fa[x]) continue;
dfs2(j);
}
dp[x] = x;
if(!son[x]) return;
int t = dp[son[x]];
while(deep[t] > deep[x] && sz[x] - sz[t] > sz[t]) t = fa[t];
dp[x] = t;
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof(h));
for(int i = 1; i <= n - 1; i ++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
dfs1(1);
dfs2(1);
for(int i = 1; i <= n; i ++) {
int x = dp[i];
ans[i].push_back(x);
int t = fa[x];
if(t && sz[i] - sz[x] == sz[x]) ans[i].push_back(t);
sort(ans[i].begin(), ans[i].end());
if(ans[i].size() == 1) printf("%d\n", ans[i][0]);
else printf("%d %d\n", ans[i][0], ans[i][1]);
}
return 0;
}