这篇本来是要在树链剖分小节中写的,但是我感觉这只是树链剖分的一个衍生物,所以另开了一篇,如果对树链剖分部分还不是太了解,请看上面的链接。
计算树中两个节点的最近公共祖先,我们一般有爬山法,Tarjan离线算法,或者是将LCA转换成RMQ来解,这里讲一讲一种新的求LCA的算法,它是基于树链剖分的。
我们先来复习一下树链剖分中各个节点所维护的信息:
1:siz[v]表示以v为根的子树的节点总数。
2:dep[v]表示v的深度。
3:son[v]表示与v在同一重链上的v的儿子节点。
4:fa[v]表示v的父亲节点。
5:top[v]表示v所在链的顶端节点。
(其实还有w[v],不过在这里我们不需要)
我们先来看看树链剖分求LCA的代码,然后再来看算法的原理。
int LCA(int a, int b) { while (1) { if (top[a] == top[b]) return dep[a] <= dep[b] ? a : b; else if (dep[top[a]] >= dep[top[b]]) a = fa[top[a]]; else b = fa[top[b]]; } }
1:如果top[a]==top[b],说明a和b在同一条重链上,显然它们中深度较小的点即为它们的最近公共祖先。
2:若果top[a]!=top[b],(说明a,b在不同的重链上)且a的深度较大,则此时a,b的LCA不可能在a所在的重链上。
因为如果a,b的LCA在a所在重链上,那么top[a]显然也为a,b的公共祖先,则若dep[up[a]]]>dep[b],则显然不可能,若dep[up[a]]]<=dep[b],则设dep[up[a]]]为d,因为d>=dep[b],所以我沿着b向上搜索,在深度d处也可以找到一个点C为b的祖先,又因为a,b不在同一条重链上,所以top[a]!=C,这就意味着在同一深度,b有两个不同的祖先,这是不可能的(因为是一棵树),所以,LCA不可能在a所在的重链上。所以我们可以将a上升到up[a]的父节点,这时a,b的LCA没有变化。
3:若果top[a]!=top[b],且b的深度较大,同理我们可将b上升到up[b]的父节点。
4: a,b不停地上升,所以一定可以找到a,b的LCA。
因为我们知道,在树中任意一点到根的路径上,重链数不会超过(logn),所以我们可以在O(logn)的时间复杂度内,将a,b的LCA求出来。
因为我们求LCA都是在重链上进行,所以结合线段树,我们可以维护树中任意两点间路径的信息了,比如两点间的最大边最小边,边权和等等,这里就不一一举例了。