性质
- 删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心;2.树中所有节点到重心的距离之和最小,如果有两个重心,那么他们距离之和相等;
- 两个树通过一条边合并,新的重心在原树两个重心的路径上;
- 树删除或添加一个叶子节点,重心最多只移动一条边;
- 一棵树最多有两个重心,且相邻。
思路
如果找到只有一个重心,那么直接删一个重心的直连边然后加回去就好了。
如果找到两个重心,那么在其中一个重心上找到一个直连点不是另一个重心,删除连另外一个就好了。
如何求树的重心?
先任选一个结点作为根节点,把无根树变成有根树。然后设 siz[x] 表示以 x 为根节点的子树节点个数。转移为 siz[x] = Σ(i = 0; i <= v[x].size()) siz[v[x][i]] ; 设 son[x] 表示删去节点x后剩下的连通分量中最大子树节点个数。其中一部分在原来 i 其为根的子树。 son[x] = max(son[x], siz[v[x][i]]) ; 另外一部分在x的“上方”子树有 n – siz[x] 个。 son[x] = max(son[x], n – siz[x]) ;
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<utility>
#include<cstring>
#include<iostream>
#define go(a,b,c) for(int a=b;a<=c;a++)
using namespace std;
int t, n, sz[100010], f1, f2, son[100010];
vector<int> v[100010];
void dfs(int x, int fa) {
sz[x] = 1;
son[x] = 0;
int len = v[x].size();
go(i, 0, len - 1) {
int to = v[x][i];
if (to == fa)
continue;
dfs(to, x);
sz[x] += sz[to];
son[x] = max(son[x], sz[to]);
}
son[x] = max(son[x], n - sz[x]);
if ((son[x] << 1) <= n) {
f2 = f1;
f1 = x;
}//用性质1判断节点x是否为重心
}
int main() {
cin >> t;
while (t--) {
cin >> n;
memset(sz, 0, sizeof sz);
memset(son, 0, sizeof son);
f1 = f2 = 0;
go(i, 1, n)
v[i].clear();
go(i, 1, n - 1) {
int x, y;
cin >> x >> y;
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1, 0);
if (!f2)
cout << f1 << ' ' << v[f1][0] << endl << f1 << ' ' << v[f1][0] << endl;
else {
int len = v[f2].size();
go(i,0,len-1)
if (v[f2][i] != f1) {
cout << f2 << ' ' << v[f2][i] << endl << f1 << ' ' << v[f2][i] << endl;
break;
}
}
}
return 0;
}