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}\))。

浙公网安备 33010602011771号