LCA问题
LCA 问题,即 Least Common Ancestors(最近公共祖先)的意思是:给定一有根树,求其两个节点最近的公共祖先;节点的祖先即从节点至根的路径上的节点的集合。
LCA 问题可以转化为 RMQ 问题求解,方法是:从根开始对树进行一次深度优先遍历,每次沿一条边到达另一节点时记录下节点的深度,这样就得到一个序列;对于问题中的两个节点,以它们的首次出现为两端的子序列中的最小值就是最近公共祖先。规模为n的 LCA 经过转化变成了规模为2n-1的 RMQ,渐进复杂度不变,然后就可以使用 RMQ 的各种方法来解答。
但是即便LCA转换成了RMQ,比较好写的ST之类方法还是O(nlogn)的,在竞赛中更好用的似乎是 Tarjan 的 O(nα(n)) 的离线算法。算法用到了并查集,在对树的一次深度优先遍历后完成,方法大致是这样的:每次访问到一个节点,对它建立一个并查集,设定它的祖先为自己;访问它的每个子节点,并在每次访问结束后设儿子的并查集的代表顶点的祖先为自己;将自己标记为黑色,对于每个与它有关的问题,若另一节点也为黑色,则这个问题的答案就是另一节点所在的并查集的代表顶点的祖先。
至于RMQ转换成LCA其实也简单,要将原数组转换成听上去有点神秘的“Cartesian Tree”(笛卡尔树)。所谓Cartesian Tree,是一个二叉树,每个节点的值大于子节点(堆序),节点的中序遍历满足原数组顺序(排序二叉树)。看到这儿我明白了——这不就一Treap么……当然,我们是不用挨个插入 Treap 的O(nlogn) 算法的。是采用自数组从左向右扫描,每次的新数沿着旧树的右路径上升至不能上升。每个节点进入和退出右路径最多一次,所以是均摊 O(n) 的。呵呵,说起来简单,我还没实现过这个呢,什么时候真闲得慌了可以写写玩玩。
LCA 的 Tarjan 算法示例程序:
ural1471.cpp
