【应用】树上问题-树的重心
树的重心是什么?
对于一棵无根树,设其中的一个节点作为根,则可以形成一棵有根树。
该树以根为分界,分为若干个子树,设其中最大子树具有的节点数为 \(x\) 。
所有节点里, \(x\) 值最小的节点就是该树的重心,也叫质心。
例如上图这棵树,以1为根时,三个子树的大小分别为3、3、2,其中最大的为3。
1就是这棵树的重心,因为以其他节点为根时,最大子树的大小都会超过3。
比如假设以3为根,则其会有两个子树,分别为{7,8}和{1,2,4,5,6,9},其中最大的子树大小为6。
重心的性质
重心相当于树的平衡点,其尽量均匀地将树分割成了若干部分。
在树分治以及树同构中,重心都具有相当重要的地位。
重心具有几个比较重要的性质:
①一棵树最少有一个重心,最多有两个重心,若有两个重心,则他们相邻(即连有直接边)。
②树上所有点到某个点的距离和里,到重心的距离和最小;若有两个重心,则其距离和相同。
③若以重心为根,则所有子树的大小都不超过整棵树的一半。否则可以通过平移使得最大子树的大小缩小至整树的一半,剩下子树的大小最大为 \(n / 2 - 1\) 。此时新平移到的点才是真正的重心。
④在一棵树上添加或删除一个叶子节点,其重心最多平移一条边的距离。
⑤两棵树通过连一条边组合成新树,则新树重心在原来两棵树的重心的连线上。
如何求解重心?
按其定义来求即可。
我们任选一个节点作为根,然后跑DFS,在过程中记录每个节点各个子树的大小。对于每个节点,其还存在一个上方的子树(即从假设根到当前节点),其大小为总节点数减去当前节点的总子树大小。
然后就能得到每个节点最大子树的节点数,取其中最小值即可。
整个算法只需要遍历一遍树,时间复杂度为 \(O(n)\)。
代码实现:
int siz[N], wgt[N]; // size and weight, weight mean max sontree
int n, mi = inf, ans; // mi=min max size, ans=重心
void dfs(int now, int fa) {
siz[now] = 1;
wgt[now] = 0;
for (int i = 0; i < e[now].size(); i++) {
int to = e[now][i];
if (to == fa)
continue;
dfs(to, now);
siz[now] += siz[to];
wgt[now] = max(wgt[now], siz[to]);
}
wgt[now] = max(wgt[now], n - siz[now]);
if (wgt[now] < mi)
mi = wgt[now], ans = now;
}