树上最近公共祖先(LCA)的算法

  有错请大力指出【鞠躬】第一次写正经博客非常慌张

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。
对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。
另一种理解方式是把T理解为一个无向无环图,而LCA(T,u,v)即u到v的最短路上深度最小的点。

——百度百科

LCA的四种算法:

  • 记录dfs序转化为rmq问题(st表)
  • tarjan算法
  • 倍增算法
  • 树链剖分

一、记录dfs序转化为rmq问题

  1.dfs序是什么?

  其实本人对dfs序的定义也不怎么清晰……望告知orz

  首先我们需要一颗树……比如说它长这样:

  

  定义rt(root)为树根,则在这棵树中rt = 1。别说了我知道图上是A

  本题需要用到的dfs序需在进入每个节点即搜完某一个儿子的时候记录这个节点。从rt开始进行dfs(深度优先搜索),则得到的dfs序列为A B D B E B F B A C G H G C A。

  且让我们跟着跑一遍。

    1.dfs(A) , 序列为A

    2.找到A的儿子B,dfs(B),序列为A B

    3.找到B的儿子D,dfs(D),序列为A B D

    4.发现D没有儿子,返回B,序列为A B D B

    5.6.7.8. dfs(E),返回B,dfs(F),返回B,序列为A B D B E B F B

    9.搜完B的儿子,返回A,序列为A B D B E B F B A

    10.11.12. dfs(C),dfs(G),dfs(H),序列为A B D B E B F B A C G H

    13.14.15 返回G,返回C,返回A,序列为A B D B E B F B A C G H G C A

  结束。

  定义x为当前节点标号,dep为当前深度,h[i]为树高,in[i]为每个节点被访问到的序号,d[i]为dfs序。

  代码如下:

void dfs(int x , int dep)
{
    d[++ cnt] = x ;
    h[x] = dep , in[x] = cnt ;
    for(int i = fst[x] ; i ; i = e[i].nx)
    {
        if(e[i].x == fa[x]) continue ;
        fa[e[i].x] = x ;
        dfs(e[i].x , dep + 1) ;
        d[++ cnt] = x ;
    }
}

  除了搜完根节点后结束dfs外,每个点被搜到和退出时均会在dfs序中加入一个数,因此该dfs序的长度为2 * n - 1。

  设点u、点v的最近公共祖先为点x,并且u在v之前被dfs到。因为x为u、v的祖先,所以dfs时先找到x,再从x往下找到u,即dfs(u)时已经过了x。记录u后,返回到x再向下找到v。故在dfs序中,in[u]和in[v]之间必出现过x。而dfs(v)后返回时,x的子树还没有被搜完,所以不会出现x的祖先。这样即可保证在in[u]、in[v]中高度最小的点即为u、v的最近公共祖先。

  这样我们有了一个正确的算法,但如果对于每个询问区间,都一位一位寻找最值,效率为O(mn),显然不能接受。

原文地址:https://www.cnblogs.com/Lucille/p/8445305.html

时间: 2024-10-12 04:13:39

树上最近公共祖先(LCA)的算法的相关文章

最近公共祖先LCA(Tarjan算法)的思考和算法实现——转载自Vendetta Blogs

最近公共祖先LCA(Tarjan算法)的思考和算法实现 LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了flase...看的时候注意一下! //还有...这篇字比较多 比较杂....毕竟是第一次嘛 将就将就 后面会重新改!!! 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先

我对最近公共祖先LCA(Tarjan算法)的理解

LCA 最近公共祖先 Tarjan(离线)算法的基本思路及我个人理解 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点. 所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径. 有人可能会问:那他本身或者其父亲节点是否可以作为祖先节点呢? 答案是肯定的,很简单,按照人的亲戚观念来说,你的父亲也是你的祖先,而

最近公共祖先 LCA 倍增算法

倍增算法可以在线求树上两个点的LCA,时间复杂度为nlogn 预处理:通过dfs遍历,记录每个节点到根节点的距离dist[u],深度d[u] init()求出树上每个节点u的2^i祖先p[u][i] 求最近公共祖先,根据两个节点的的深度,如不同,向上调整深度大的节点,使得两个节点在同一层上,如果正好是祖先结束,否则,将连个节点同时上移,查询最近公共祖先. void dfs(int u){ for(int i=head[u];i!=-1;i=edge[i].next){ int to=edge[i

最近公共祖先 LCA Tarjan算法

来自:http://www.cnblogs.com/ylfdrib/archive/2010/11/03/1867901.html 对于一棵有根树,就会有父亲结点,祖先结点,当然最近公共祖先就是这两个点所有的祖先结点中深度最大的一个结点. 0 | 1 /   \ 2      3 比如说在这里,如果0为根的话,那么1是2和3的父亲结点,0是1的父亲结点,0和1都是2和3的公共祖先结点,但是1才是最近的公共祖先结点,或者说1是2和3的所有祖先结点中距离根结点最远的祖先结点. 在求解最近公共祖先为问

POJ 1330 LCA最近公共祖先 离线tarjan算法

题意要求一棵树上,两个点的最近公共祖先 即LCA 现学了一下LCA-Tarjan算法,还挺好理解的,这是个离线的算法,先把询问存贮起来,在一遍dfs过程中,找到了对应的询问点,即可输出 原理用了并查集和dfs染色,先dfs到底层开始往上回溯,边并查集合并 一边染色,这样只要询问的两个点均被染色了,就可以输出当前并查集的最高父亲一定是LCA,因为我是从底层层层往上DSU和染色的,要么没被染色,被染色之后,肯定就是当前节点是最近的 #include <iostream> #include <

【算法】树上公共祖先的Tarjan算法

最近公共祖先问题 树上两点的最近公共祖先问题(LCA - Least Common Ancestors) 对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u和v的祖先且x的深度尽可能大.在这里,一个节点也可以是它自己的祖先. 例如,如图,在以A为根的树上 节点 8 和 9 的LCA为 4 节点 8 和 5 的LCA为 2 节点 8 和 3 的LCA为 1 在线算法与离线算法 区别就在于是同时处理询问后输出还是边询问边输出 在线算法是读入一组询问,查询一次后紧

最近公共祖先(LCA)问题

描述 对于有根树T的两个节点u和v,最近公共祖先LCA(T,u,v)表示一个节点x满足x是u,v的公共祖先且x的深度尽可能大. 算法 求解LCA问题主要有三种解法,分别是暴力搜索,Tanjar算法,最后一种是转化为RMQ问题,用DFS+ST算法来求解 暴力搜索 如果数据量不大的时候可以采用暴力搜索法.先将节点u的祖先节点全部标记出来,然后顺着节点v沿着父亲节点的方向向上遍历,直到遍历到一个被标记的节点,这个节点即为所求节点.或者分别获取u,v到根节点的路径P1,P2,可以将这两条路径看做两个两个

在线查询树上最近公共祖先模板

在线查询树上最近公共祖先 标准题目 第一行有2个整数n和q:n代表树的结点数量,q代表询问的次数接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边.然后给出q组询问,每组询问由两个整数a和b构成,询问节点a和b的最近公共祖先. 样例数据 input:8 31 31 23 43 53 62 72 84 56 75 8 output:311 代码 1. 邻接表建图 1 #include <iostream> 2 #include <cstdio> 3 #include <

POJ 1470 Closest Common Ancestors【最近公共祖先LCA】

题目链接:http://poj.org/problem?id=1470 题目大意:给出一棵树,再给出若干组数(a,b),输出节点a和节点b的最近公共祖先(LCA) 就是很裸的LCA,但是我用的是<挑战程序设计竞赛>上的"基于二分搜索的算法求LCA",我看网上用的都是tarjan算法.但是我的代码不知道为什么提交上去 wrong answer,自己想的很多测试数据也都和题解结果一样,不知道错在哪里,所以把代码保存一下,留待以后解决...... 如果读者有什么建议,希望提出来,