如果在树 T 中删去某个结点 v 后,得到的图 T∖{v} 中每个连通分量的大小均不超过原树结点数的一半,就称这个结点 v 为整棵树的 重心(centroid)。删去某一结点后得到的最大连通分量的大小也称为该结点的 重量(weight)。利用这一概念,重心的定义可以叙述为重量不超过树结点数的一半的结点。
「子树」
本文可能同时涉及无根树、有根树,以及将有根树换根到非根结点得到的树。为了避免混淆,本文将用 T 表示无根树,用 T(v) 表示以结点 v 为根的有根树。本文中提到的「子树」均是指 有根树 中,一个结点及其所有子孙结点构成的树。有根树 T(v) 中,结点 u 对应子树记为 Tu(v)。这样定义的子树当然包括整棵树本身。如果要明确不包括整棵树本身,将称呼它为「真子树」。
在实际求解重心或处理某些问题时,通常存在一个默认的树根。此时,将非根结点 v 删掉得到的连通分量中,除了该结点的子结点对应子树外,还有一棵「向上」的子树。此时,设结点 v 的父结点为 u,这棵「向上」的子树就是 Tu(v)。本文在提及这类子图时,会显式地称呼它为「向上」的子树。若无特殊说明,本文提及的子树均不包括这类「向上」的子树。
首先,引入一些记号。有根树和无根树两个版本的表述显然是等价的。定义 W(x)=maxu∼x∣Tu(x)∣,其中,u∼x 表示 u 和 x 相邻。定义 S(x)=∑u∈Td(u,x),其中,d(u,x) 表示结点 u 和 x 之间的距离。那么,定义 1 相当于要求 W(v)≤∣T∣/2,定义 2 相当于要求 v∈argminx∈TW(x),定义 3 相当于要求 v∈argminx∈TS(x)。需要证明的是,这三个条件是等价的。
将 S(x) 理解为以 x 为根时的结点深度和,考虑它在树根从结点 v 换为相邻结点 u 时发生的变化。注意到,在树中删去边 (v,u) 后,得到的两个连通分量分别是子树 Tv(u) 和 Tu(v)。在换根前后,子树 Tv(u) 中每个结点深度都增加 1,子树 Tu(v) 中每个结点深度都减少 1,所以,深度和的变化为
ΔSv→u=S(u)−S(v)=∣Tv(u)∣−∣Tu(v)∣=∣T∣−2∣Tu(v)∣.
所以,定义 1 的条件相当于要求 ΔSv→u≤0 对 v 的所有相邻结点 u 都成立,也就是说,v 是 S(x) 的极小值点。
转而设 v 是 S(x) 的(一个)最小值点(即定义 3),它必然存在,且一定是极小值点。考虑以 v 为根的有根树 T(v)。设 u=v 是一个非根结点,且从 v 到 u 的有向路径上,结点 v 的后一个结点为 y(可能就是 u),结点 u 的前一个结点为 x(可能就是 v)。那么,因为 Tu(x)⊆Ty(v),所以有
2∣Tx(u)∣=2∣T∣−2∣Tu(x)∣≥2∣T∣−2∣Ty(v)∣≥∣T∣.
其中,最后一步用到 v 是 S(x) 的极小值点这一事实。此时,有两种情形:
存在结点 u 使得 2∣Tx(u)∣=∣T∣ 成立。此时,根据上述不等式,必然有 (x,u)=(v,y),且 ∣Tv(u)∣=∣Tu(v)∣=∣T∣/2。也就是说,使得等式成立的结点 u 只能有一个,且它必然与 v 相邻。此时,对于所有其他结点 u′=u,v,必然存在 x′∼u′ 使得 ∣Tx′(u′)∣>∣T∣/2 成立。满足条件 1 的结点集合为 {v,u}。
树 T 只有一个重心 v。设 x 为新添加的叶子结点,且在新树中删去结点 v 得到的图 T∪{x}∖{v} 中,x 所在连通分量为 B∪{x}。因为 v 是树 T 的唯一重心,所以 2∣B∣<∣T∣,也就是 2∣B∣+1≤∣T∣。进而,有
2∣B∪{x}∣=2(∣B∣+1)≤∣T∣+1=∣T∪{x}∣.
因此,v 仍然是新树 T∪{x} 的重心。即使新树的重心不唯一,它也必然是 v 的相邻结点。因此,重心至多移动一条边。
树 T 有两个重心 u,v。此时,删去 (u,v) 得到的两个连通分量 Tu(v) 和 Tv(u) 大小相等,均为 ∣T∣/2。不妨设新添加的叶子结点 x 连接在 v 所在的连通分量 Tv(u) 上。那么,因为
∣Tv(u)∪{x}∣=∣T∣/2+1>(∣T∣+1)/2=∣T∪{x}∣/2,
所以 u 不再是新树的重心。反过来,因为删去 v 后,仍然有大小为 ∣T∣/2 的连通分量 Tu(v),而其他连通分量的大小之和为
∣T∪{x}∣−1−∣Tu(v)∣=∣T∣/2≤∣Tu(v)∣,
所以,v 仍然是新树的重心。因为新树的结点数是奇数,重心必然唯一。所以,重心也至多移动一条边。
总结两种情形的分析可以发现,新树的重心一定在旧树的重心与新添加的叶子结点的路径上。
性质 3 可以通过归纳法说明。设 T 和 T′ 连接时,新添加的边为 (x,y),且 x∈T,y∈T′。不妨设新树的(一个)重心在 T 中。考虑从树 T 开始,将树 T′ 中结点逐一添加为其叶子结点的过程。可以归纳地证明,树的(一个)重心始终在连接树 T 的重心与结点 x 的路径上。归纳起点显然。假设直到某一时刻为止,命题仍然成立。设此时重心为 v。由性质 2 的分析可知,新树的重心必然在连接新添加结点和当前重心 v 的路径上,又因为它不会移动到树 T 外,所以,只需要考虑路径与树 T 的公共部分,即连接当前重心 v 和结点 x 的路径上。根据归纳假设,v 就在连接树 T 的重心和结点 x 的路径上,所以,新树重心也必然在连接当前重心 v 和结点 x 的路径上。由归纳法可知,命题成立。
性质 4 只需要结合 重链剖分性质 即可说明。设树 T 的(一个)重心为 v。结点 v 是根结点时,命题显然成立。下面设 v 是非根结点,u 是它的父结点。因为 ∣Tu(v)∣≤∣T∣/2,所以,v 所在子树大小至少是 ∣T∣/2。但是,只要它到根结点的路径上经过一次轻边,它所在子树大小将严格小于 ∣T∣/2,矛盾。所以,它必然在根结点所在重链上。进而,根据重链的定义,根结点重子结点对应子树中根结点所在重链就是原树中根结点所在重链的一部分;而且,根据性质 3,在重子结点对应子树上添加重子结点及其所有轻子结点对应子树后,重心位置将沿着当前重心和根结点路径移动,所以新重心必然是旧重心的祖先。
const int N = 50005;int n, siz[N];long long dp[N], ans[N];vector<int> g[N], centroids;// 求 1 号节点到所有其他节点的距离和void dfs1(int u, int fa) { siz[u] = 1; dp[u] = 0; for (int v : g[u]) { if (v == fa) continue; dfs1(v, u); siz[u] += siz[v]; dp[u] += dp[v] + siz[v]; // 子树节点到 u 的距离和 }}// 通过换根 DP 求所有节点为树根时对应的距离和void dfs2(int u, int fa) { for (int v : g[u]) { if (v == fa) continue; ans[v] = ans[u] - siz[v] + (n - siz[v]); dfs2(v, u); }}// 求树的重心void get_centroids() { dfs1(1, 0); ans[1] = dp[1]; dfs2(1, 0); long long mini = std::numeric_limits<long long>::max(); for (int i = 1; i <= n; i++) { if (ans[i] < mini) { mini = ans[i]; centroids = {i}; } else if (ans[i] == mini) centroids.push_back(i); }}