20251220 - LCA 总结

比赛链接:https://vjudge.net/contest/776080。

大家好我是一只小菜喵。

咋了 rk13 还不菜吗,好吧我只能告诉你我还没补题(摊手

最近公共祖先(LCA)

定义

在有根树上,对于两个节点 \(u\) 和 \(v\),这两个节点的所有公共祖先中,距离根节点最远的节点,也就是深度最深的节点,或者说是距离 \(u\) 和 \(v\) 最近的节点,就称它为 \(u\) 和 \(v\) 的最近公共祖先,全称 Lowest Common Ancestor,简称 LCA。

暴力求解

咳咳,首先来考虑一下用最暴力的方式,来求两个节点 \(u\) 和 \(v\) 的 LCA。

首先我们肯定需要维护每个节点 \(x\) 的深度 \(dep_x\) 以及它的父亲节点 \(fa_x\)。显然的,距离根节点越远的节点深度越大。有深度推导公式 \(dep_x = dep_{fa_x} + 1\)。

接着来考虑具体的求解。假设 \(dep_u \ge dep_v\)。

考虑先把 \(u\) 的深度调整至与 \(v\) 相等。但你肯定不能更改 \(dep_u\) 的值啊,不然树的结构就乱了,所以当然是让 \(u\) 沿着它的一层层祖先节点往上跳咯。直到 \(dep_u = dep_v\),我们就结束这个过程。

接下来就是求最近公共祖先的核心步骤了。只要 \(u \not= v\),我们就不断让 \(u = fa_u\) 并 \(v = fa_v\),沿着祖先节点一直往上跑,直到相遇,相遇在的这个节点 \(u\)(或者 \(v\),没区别,因为它俩一样的)就是原 \(u\) 和 \(v\) 的最近公共祖先 LCA。

但是我们发现这个东西的时间复杂度最坏是 \(O(D)\) 的,其中 \(D\) 为这棵树的深度,最大可达到 \(n\)。这可不行!要是多跑上几次就炸了。咋办呢?

倍增优化

不用担心哈哈,你的好朋友倍增来了。

使用倍增优化可以让求解 LCA 的速度从 \(O(n)\) 降低到 \(O(\log n)\),是不是很高效呢?

高效是高效但你还没说怎么用(

别急。既然要倍增,那我们就该改变我们存储祖先节点的方式。先前是只存了自己的父亲节点,现在我们让 \(f_{x,i}\) 存储 \(x\) 往上跳 \(2^i\) 步的祖先节点的编号,如果跳不了这么多步就是 \(0\)(其实是根也是可以的哈)。不难发现 \(f_{x,0}\) 就是 \(x\) 的父亲节点。

那么求 LCA 那里的两次跳跃就都可以用倍增来实现了,注意后面那一次跳跃是跳到最后一个不相同的祖先节点那个位置,也就是最后一个 \(u \not= v\),再上一步就是 \(u=v\) 了,所以返回的是 \(f_{u,0}\)(等同于 \(f_{v,0}\))。

dfn 上 ST

还有一种 \(O(n \log n)\) 预处理,单次询问 \(O(1)\) 查询的求 LCA 的方法,那就是在 dfn 上做 ST。

首先,我们肯定要先求出每个节点的 dfn 序,记录为 \(dfn_x\)。这里的预处理时间复杂度是 \(O(n)\) 的。

接着我们考虑在 dfn 上做 ST,\(st_{i,j}\) 表示 dfn 中以 \(i\) 为左端点且长度为 \(2^j\) 的区间的深度最浅的节点下标,转移需要用到 if 判断或者三目运算符,比最通常的 ST 稍微复杂些。

那么之后就可以快速地利用 ST 查找 LCA 了。

例题选讲

D - Milk Visits S

这个在以往的 LCA 的基础上多维护了两个 \(dis\) 数组,存储路径上出现的两种不同品种奶牛的个数,计算就是 \(dis_u + dis_v - 2 \times dis_{lca} + a_{lca}\),因为这边用的是点权,所以还得加上个 \(a_{lca}\)。

E - 专心OI - 找祖先

非常不好奇地问一句这题和 LCA 有什么关系啊??

呃,我们发现,每次要么是找两个不同子树内的,要么就是自己和某个值。所以只需要维护以每个节点为根的子树大小就可以啦!

注意这边求的是有序对,然后一定要注意题目是给定了根是 \(R\) 的别自己瞎定!!!别问我是怎么知道的呜呜。就因为这个白吃了两发罚时呜呜。

F - Cow Politics G

首先有一个结论:不论是哪个政党,那个距离相隔最长的点对中,一定包含这个政党中深度最深的那个点。

于是然后利用 LCA 和深度情况 \(dep\) 枚举节点,匹配,对应,就可以找出每个政党相距最远两个点的距离了。

H - Merging Towers

因为每一座塔的圆盘都是从小到大按顺序的,所以我们每一次移动都一定可以在原有的基础上至少多匹配一个,所以困难值有求解公式,其实就是大小相邻却不在同一座塔的圆盘的对数。

然后每一次合并也就是判断某个塔里是否包含与当前塔里的圆盘的大小相邻的圆盘,让困难值降低这么多。

因为无论谁合并到谁上对答案的影响相同,所以我们就考虑启发式合并,也就是将小的合并的大的上面,这样就能将复杂度降到 \(O(\log n)\),均摊 \(O(n \log n)\) 是过得去的。

I - The Shortest Statement

观察到 \(m-n \le 20\),这一定是个特别棒的性质,因为这说明非树边最多 \(21\) 条。那么先搞出来一棵树,然后把非树边的两个点标记为特殊点。最多 \(42\) 个特殊点。

那么 \(u\) 到 \(v\) 的最短路就分成两种情况:只经过树边,或者经过了非树边。

只经过树边的,用 LCA + \(dis\) 随便搞,而经过树边的呢,你忘了,就 \(42\) 个特殊点诶,分别从每个特殊点出发向全局跑一次最短路肯定是不过分的。

那么就做完了。

总结

Lowest Common Ancestor,最近公共祖先,即 LCA,其(在加了倍增优化之后)能够在最劣 \(O(\log n)\) 的时间复杂度下求出两个节点的 LCA。通常的用途例如求树上两点的距离、求一条树边上的点权或边权之和。当在树上求距离或权值等信息时,LCA 频繁出场,当然有的时候也会融和图一起使用。总之,它是一个很好用的求树上距离或权值的算法。

posted @ 2025-12-22 22:13  嘎嘎喵  阅读(43)  评论(3)    收藏  举报