关于 LCA 这东西太平常了 …… 这里就给出几种做法!!
倍增
我相信各位 dalao 学 LCA 的时候必定第一个学的就是倍增。
倍增其实就是将暴力向上跳的过程改为跳 $2$ 的次幂。
这东西也没啥好说的,大概流程就是大力 DFS 一遍,把 $2$ 的次幂的父亲都给处理出来,然后在线搞 LCA。
LCA 里面就是将两个节点先提到同一个深度,然后不停地向上跳,最终会跳到 LCA 的儿子。
至于为什么正确么 …… LCA 必定只有一个父亲,所有在 LCA 上面的全都是同一个父亲。
所以这样就直接判跳上去的是否是同一个父亲就可以了。
总时间复杂度 $O((n+q) \log n)$。
1 |
|
树剖
树剖这个东西其实没啥好说的,实际跟倍增差不多,就是把 $2$ 的次幂改成链顶端。
虽然说也是 $((n+q) \log n)$,但它能维护树上的链和子树信息 然而要多个 $\log n$。
还有一个优势就是常数比较小。
1 |
|
st 表
st 表的优势在于可以 $O(1)$ 求 LCA,但它预处理的复杂度还是 $O(n \log n)$。
主要的就是对树进行一次遍历,记录 DFS 序,然后用新的编号表示,越小的也意味着深度越浅。
然后 ST 表求出两个节点最先进入的区间里面的最小值。就这样完事了。
至于为什么正确 …… (OI 里拿分就可以了,管什么正确性呀!!!)。
这个我也不太好证明,你就想,当前遍历到两个节点必然是要经过 LCA 的对吧,那肯定是不会到上面的,而 LCA 在这里必然是最小值。
所以显然酱紫是可以的,注意 DFS 序用两倍空间。
总时间复杂度 $O(n \log n + q)$。
1 |
|
tarjan
tarjan 是一种离线求 LCA 的方法,但实际能做到线性的复杂度!!!
就冲线性这一点,已经足够优秀了,(我讲的全是现在主流的,有一些极致优化我当然不会)。
大概流程就 DFS ,然后走到当前节点,如果说当前节点是要统计答案的,并且另一个也遍历好了。
举个例子 $(x,y)$ 假定当前已经遍历到了 $x$ 判断 $y$ 是否遍历过,如果是的,那么当前就直接找祖先,这可以用并查集来维护。
正确性就是当前遍历到的显然是最近的公共祖先,因为不会往上回溯,和 ST 表其实差不多。
1 |
|
至此 LCA 的各种优化已经全部列出 (如果有遗漏的肯定是超过了 OI 的范畴,就基本不会用的)。
如果有问题还请指出!