1.RMQ+ST
首先注意这个算法的要素:结点编号,dfs序,结点深度。
首先dfs,求出dfs序,同时求出每个结点的深度。然后st算法,维护深度最小的结点编号(dfs序也可以,因为他们俩可以互相转换,只要不是深度就行了)。这样后面查询的时候才知道lca是哪个结点。如果维护的是深度。。那就不知道了。
感觉这个算法的精髓在于:两个节点的dfs序间最小深度的结点一定是它们的lca(不会证)。至于结点编号和dfs序如何转换。。dfs序转换成结点编号很简单,数组取一下就行了。然而结点编号转换成dfs序需要对于每个结点存一个first,表示每个结点第一次在dfs序中出现的位置。为什么这样。。因为无论是维护还是查询,它和其他位置都还是一样的,没影响。
所以只要认清三个要素,然后清楚它们如何转换就行了。
时间复杂度。。预处理O(nlogn),查询O(1)吧。预处理上界紧。
2.树剖
树剖思想很好理解。。顺着链往上找就行了。注意两个结点同链时要取深度低的。
时间复杂度:预处理O(n),查询O(logn)。上界都松。
3.倍增
倍增也就是先预处理一下,然后对于一个查询x,y,先把x,y跳到同一高度,再一起进退。。好理解吧。
时间复杂度:预处理O(nlogn),查询O(logn)。上界紧。。(感觉这个好蒟蒻,但是好写)
4.tarjan
前面都是在线。。这个是秒杀一切的离线算法。
它利用了dfs的性质。首先对于树dfs。当一个结点的一个孩子dfs完以后,再把它的孩子通过并查集并到它上面。注意不能按秩合并。。因为这个结点的孩子都遍历完以后,会查询与这个结点相关的询问。如果那个结点访问过,就查询那个结点的爸爸的爸爸的爸爸祖先,那一定是它们的lca。
至于为什么不能立即合并,那是因为tarjan需要将子树独立,不然查询到的就是目前访问到的最低结点,而不是对于查询的lca了。
时间复杂度:O(n)。上界。。紧不紧无关紧要。
得出结论:如果数据不大的话,用好写的倍增。不然支持离线用tarjan,只能在线的话,查询多用RMQ+ST,节点多用树剖!