Skip to content

算法Algorithm

重心分解与最近标记点

Centroid decomposition · Centroid tree · 点分治

反复删除使剩余分量至多减半的顶点,保存原树距离标签,用对数条分隔点记录精确回答只增标记的最近点查询。

一棵树上陆续开放一些服务点。每开放一点,之后的查询都要回答:“离我最近的开放点有多远?”每次从查询位置重新遍历整棵树当然能算对,但树很大而查询很多时,这样做会反复走同样的边。重心分解给每个位置准备一条短清单:只检查这些分隔点,就足以找到一条真正达到最短距离的路线。

形式陈述 ​

原树与分解树是两套父子关系 ​

输入是有限非空无向树 T,顶点编号为 0,…,n−1,每条边有非负整数长度。距离 d(u,v) 是原树唯一简单路径的长度和。零长边允许,不同顶点的距离可以为零;顶点身份仍然不同。树的拓扑和长度在整个查询阶段固定。

对含 m 个顶点的连通分量 C,若删除顶点 c 后每个分量至多含 m/2 个顶点,称 c 为 C 的重心。这里平衡的是顶点数,不是边长,也不是当前标记数。找到 c,把它设为这一层的分解根,删除它,再对各剩余分量递归。得到的新树 TC 使用同一批顶点;各子分量的重心以 c 为父亲。[1]

有根树的父子与祖先关系分别适用于原树临时选定的根,以及这棵分解树。两者不能共用一个 parent 数组。分解边不必是原树的边,它也不代表长度为一的真实道路。

为每个顶点 v 保存标签表

L(v)={(c,d(v,c)):c 是 v 在 TC 中的祖先,含 v}.

标签里的距离始终在原树上计算。构建时,每选出一个重心 c,就在当前分量中从 c 遍历一遍,给其中每个顶点追加一个标签,然后才把 c 排除出后续分量。无须对每对 (v,c) 再调用一次最短路或LCA查询。

如何找出一个重心 ​

在当前分量任选临时根,计算每点的子树大小 size(v)。删除 v 后,向孩子一侧的分量大小分别为各个 size(child),向父亲一侧为 m−size(v)。于是检查

max({m−size(v)}∪{size(x):x 是 v 的孩子})≤m/2.

至少有一个点通过检查。证明可以从任意点出发:若它不满足条件,就沿唯一一个超过 m/2 的分量走到邻点。跨过这条边后,反方向的分量小于 m/2,不会再走回来;一条有限树中不回头的行走必停止,停止点就是重心。这里“唯一”来自两个互不相交分量不可能都超过一半。

参考器检查全部候选,若有两个则取编号较小者,使输出可重复。这个平局规则只固定一种分解,不参与距离正确性。

只增加标记的完整接口 ​

初始没有标记。对每个重心 c,维护 B[c]:所有已经标记、且标签表含 c 的顶点中,到 c 的最小距离;没有这样的点时为 +∞。因此 B[c] 只汇总这一层原分量里的标记点,不能把它解释成全树所有标记点的最小值。

text
mark(v):
  对 (c,delta) 属于 L(v): B[c] = min(B[c],delta)

nearest(u):
  返回 min{delta+B[c] : (c,delta) 属于 L(u)}

重复标记不改变集合。参考器额外保存达到最小值的标记身份,距离并列时选编号较小者;空集合返回 None,不伪造某个有限的大数。本接口没有取消单个标记的操作。reset 可以清空全部标记及 B,费用为 O(n),之后继续使用原标签。

直觉

删除重心会把道路网络分成几支。若查询点和某个服务点在不同支,它们的唯一路径必须经过这个重心;若还在同一支,就继续到下一层。最终总会遇到一个恰好位于两点真实路径上的共同分隔点。

其他祖先可能让路线绕远。例如从同一支的两个点先绕到顶层重心再回来,会重复走一段路。非负边长使这种绕路不会比真实路径更短,所以可以把所有候选取最小:绕远者不会造成低估,而其中至少一个候选不绕远。

分解边不是原树道路,标签保存的是原树距离
例子与边界

十一个顶点,三次开放 ​

固定以下原树,括号内为边长:

text
0—1(2)  0—2(3)  1—3(1)  1—4(4)  3—5(2)
3—6(1)  2—7(2)  7—8(3)  7—9(1)  9—10(2)

删除0后两支各有5点,所以先选0。左支重心为3,删除后大小为2、1、1;右支重心为7,删除后大小为1、1、2。两点分量按编号平局选1或9。最终分解父亲为

text
0:无;1:3;2:7;3:0;4:1;5:3;6:3;7:0;8:7;9:7;10:9

第一次标记4。它的标签是 (0,6),(3,5),(1,4),(4,0)。查询6时,自己的标签为 (0,4),(3,1),(6,0):

经哪个重心 查询点到重心 B[c] 候选距离
0 4 6 10
3 1 5 6
6 0 +∞ 无候选

答案为6,真实路径是 6→3→1→4,长度 1+1+4=6。顶层候选10多走了 1→0→1,不能只查顶层摘要。

接着标记8,查询10。经0的候选为 8+6=14,经7为 3+3=6,经9和10没有标记摘要。最近点为8,路径 10→9→7→8 长 2+1+3=6。最后标记6,再查询5,经0的候选为 5+4=9,经3为 2+1=3;最近点变为6。三次答案依次为6、6、3,不是只对一份静态标记集合求值。

三个不能偷偷扩大的条件 ​

负边会破坏下界。 在路径 0−1−2 上把两边长度设为 −2,1,重心为1,只标记0并查询0。经1的候选是 −2+(−2)=−4,却小于自身的真实简单路径距离0。因此本参考器在入口拒绝负边;“树上没有圈”不能挽救这次绕路比较。

取最小不能撤销。 若只标记4后又把4取消,旧 B 仍保存4提供的距离,查询会回答一个已经不存在的点。支持删除需要保存更多候选,例如具有删除能力的有序多重集合,并重新计费。不能把 marked[4] 改为假就声称实现了删除。

分解静态,标记动态。 改变边长会使距离标签失效;link/cut还会改变分量与分隔关系。本接口必须重建。与重链剖分不同,这里没有把任意路径变成若干连续数组区间;与Link–Cut Tree也不同,它不承诺维护动态森林。

推论与应用

为什么最小候选恰好是答案 ​

设标记集合为 M≠∅,真实答案 D=minv∈Md(u,v)。先证明算法不会低估。一个有限的 B[c] 由某个真实标记点 v 达到,所以相应候选为 d(u,c)+d(c,v)。原树上这段拼接行走比唯一路径可能多走往返边,但非负长度保证

d(u,c)+d(c,v)≥d(u,v)≥D.

再取真正最近的标记点 v∗。让 c 是 u,v∗ 在分解树上的最近公共祖先。若 c 等于其中一点,它显然在原路径上;否则两点在删除 c 后进入不同分量,原路径也必须经过 c。二者标签都含 c,且 B[c]≤d(c,v∗),于是

d(u,c)+B[c]≤d(u,c)+d(c,v∗)=d(u,v∗)=D.

上下两向给出相等。这里的分解树LCA只用于证明,查询程序不必真的求它;逐一检查短标签已经覆盖这个见证。返回的标记身份也是真实最近点:若其经 c 的候选等于 D,它的真实距离夹在 D 与该候选之间,只能同为 D。

构建、操作与输出分别花多少钱 ​

每层剩余分量至多减半,任一顶点最多出现在 1+⌊log2⁡n⌋ 层。因此标签总数为 O(nlog⁡(n+1)),标记与查询都至多检查这一条对数长祖先链。固定字长距离、数组访问及整数运算下,完成标签后每次操作最坏 O(log⁡(n+1)),B 和标记数组另占 O(n)。

构建采用分治法,逐分量重新求子树大小并填写距离标签。每层各分量的顶点不相交,总遍历工作为 O(n),层数对数,故总时间和标签空间为 O(nlog⁡(n+1))。参考器保留原邻接表并跳过已删除点;即使小分量仍会检查通向旧重心的邻接项,同一层所有被访问顶点的原度数总和仍不超过 2(n−1),因此这些检查也被上述总界覆盖。输入连通性、边数和权值检查为 O(n)。

参考器用字典记录一个临时分量的父亲和大小,这部分采用期望常数散列表操作;构建的相应界为期望 O(nlog⁡(n+1))。标签建好后的 mark/nearest仅访问列表,仍有上述最坏界。若整数可任意长,应再乘实际加法与比较的位成本。输出全部标签本身已可能需要 Θ(nlog⁡n) 项;只构造分解树另有线性算法,[1]并不意味着本页带全距离标签的输出也能无条件线性完成。

程序用显式栈遍历原树,长链不会用掉同样深度的宿主递归栈。全点对距离、全部标记集合和故障输入检查是测试工具,不属于单次在线查询的费用。

共同终点要求交出每层分量大小、三个查询的所有候选及真实路径。完整参考器还用同一原树构造虚树:后者按一次查询的少数终点压缩道路,本页则预先给每个顶点保存分隔点标签,两种缩小工作量的方法有不同接口。

参考资料

[1] Davide Della Giustina、Nicola Prezza、Rossano Venturini,A new Linear-time Algorithm for Centroid Decomposition,SPIRE 2019,§1、PDF pp.1–2:重心定义、递归分解与传统 O(nlog⁡n) 构建;论文主体另给线性算法。本页实现传统构建及自含证明的只增标记接口,不声称实现该线性改进。

关系图谱4 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系

使用的工具