树的重心
定义 1:以树的重心为根时,所有子树的大小都不超过 \(\lfloor\frac{n}{2}\rfloor\);
定义 2:树的重心是 \(\max_{v\in to[u]}{sz[v]}\) 最小的一个节点 \(u\);
定义 3:树的重心是 \(\sum{dis(i,u)}\) 最小的一个节点 \(u\);
定义 4:树的重心是最深的满足 \(sz[x]\ge \lceil \frac{n}{2}\rceil\) 的点;
性质 1:树的重心不超过两个,若有两个重心则二者相邻;
性质 2:重心一定在根节点所在的重链上;
性质 3(可合并性):一条边连接两棵树,新树的重心在连接原来两棵树的重心的路径上;添加一个叶子最多让重心移动一条边;
例题
题目大意
给定一棵树,求出以每个点为根的子树的重心。
根据性质 2,我们可以预处理出每个节点的重儿子 \(son[u]\),重心就位于这条重链中。
根据定义 3,可以取出 \(son[u]\) 子树的重心 \(p\),尝试将 \(p\) 向父亲方向移动一条边,判断是否更优。
重心移动距离不超过 \(n\),因此时间复杂度为 \(O(n)\)。
代码
| #include<iostream>
using namespace std;
const int N = 3E5 + 10;
struct Edge {
int v, next;
} pool[2 * N];
int nn, head[N];
void addEdge(int u, int v) {
pool[++nn] = {v, head[u]};
head[u] = nn;
}
int n, q;
int sz[N], son[N], ans[N], fa[N];
void dfs1(int u) {
sz[u] = 1;
for(int i = head[u]; i; i = pool[i].next) {
int v = pool[i].v;
fa[v] = u;
dfs1(v);
sz[u] += sz[v];
if(!son[u] || sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u) {
if(head[u] == 0) {
ans[u] = u;
return;
}
for(int i = head[u]; i; i = pool[i].next) {
int v = pool[i].v;
dfs2(v);
}
ans[u] = ans[son[u]];
while(sz[u] - sz[ans[u]] > sz[ans[u]]) ans[u] = fa[ans[u]];
}
int main() {
cin >> n >> q;
for(int i = 2; i <= n; i++) {
int fa;
cin >> fa;
addEdge(fa, i);
}
dfs1(1);
dfs2(1);
for(int i = 1; i <= q; i++) {
int x;
cin >> x;
cout << ans[x] << '\n';
}
return 0;
}
|