基本概念

LCA:树上的最近公共祖先,对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。

RMQ:区间最小值查询问题。对于长度为n的数列A,回答若干询问RMQ(A,i,j),返回数列A中下标在[i,j]里的最小值下标。

朴素LCA算法

求出树上每个结点的深度。

对于查询LCA(u,v),用p1、p2指向将u、v,将p1、p2中深度较大的结点不断指向其父结点,直到p1、p2深度相同。

之后p1、p2同步向上移动,直到p1=p2,此时p1、p2所指向的结点就是LCA(u,v)。

LCA向RMQ转化

对有根树进行DFS,将遍历到的结点按顺序记录下来,将会得到一个长度为2N-1的序列,称之为T的欧拉序列F。

每个结点都在欧拉序列中出现,记录结点u在欧拉序列中第一次出现的位置为pos[u]。

记录结点u的深度为dep[u],在深度序列中记录欧拉序列中的结点的深度B[1...2N-1]。

根据DFS的性质,对于两结点u、v,从pos[u]遍历到pos[v]的过程中会经过LCA[u,v],它的深度是深度序列B[pos[u]...pos[v]]中最小的。

那么求LCA(T,u,v),就等价于求RMQ(B,u,v)。

LCA的Tarjan算法

解决LCA问题的Tarjan算法利用并查集在一次DFS(深度优先遍历)中完成所有询问。它是时间复杂度为O(N+Q)的离线算法,这里的Q表示查询次数。

算法DFS有根树T,定义从根节点到当前正在遍历的结点u的路径为活跃路径P。

对于每个已经遍历过的结点x,我们使用并查集将其连接到P上距离其最近的结点F(x)。

记录与u有关的询问集合为Q(u)。

对于Q(u)中的任意一组询问LCA(u, v),如果v已经遍历过,那么答案即为F(v)。

我们只需要维护当前所有以遍历结点的F即可。

代码流程Tarjan_DFS(u):

  1. 创建并查集u
  2. 遍历Q(u)中的所有询问(u,v),如果v已经被标记,则Answer(u,v)=v所在集合的根。
  3. 对于u的每一个儿子v,调用Tarjan_DFS(v)。合并u与v所在的集合,设根为u。
  4. 标记u。

倍增LCA

与RMQ的ST算法类似,我们令F[i][0]为结点i的第2k个父结点。

则F[i][0]为i的父结点,令w为i的第2k-1个父结点即w=F[i][k-1],那么w的第2k-1个父结点就是i的第2k个父结点即F[i][k]=F[w][k-1]。

在查询LCA时,与朴素LCA类似,先将深度较大的结点u提升到与v的深度相同,而这一次我们利用倍增法,一次提升2k个父结点,加快了算法的效率。

之后,两个结点同时提高2k(k是使2k<=dep[u]最大的正整数)。直到u、v到达同一个结点。那么这个结点就是LCA(u,v)。

倍增法的优点在于,除了能求出LCA(u,v),还可以对树上的路径进行维护。

例如要求出结点u到结点v路径上最大的边权w,我们可以在预处理F[i][k]时,用一个数组maxCost[i][k]记录结点i到它的第2k个父结点的路径上最大的边权。

那么在查询LCA(u,v)的过程中,求出u、v到公共祖先的路径上的最大边权,即u到v的路径上的最大边权。

  1. void preprocess(){
  2. for (int i=;i<=n;i++){
  3. anc[i][]=fa[i];
  4. maxCost[i][]=cost[i];
  5. for (int j=;(<<j)<n;j++) anc[i][j]=-;
  6. }
  7. for (int j=;(<<j)<n;j++){
  8. for (int i=;i<=n;i++){
  9. if (anc[i][j-]!=-){
  10. int a=anc[i][j-];
  11. anc[i][j]=anc[a][j-];
  12. maxCost[i][j]=max(maxCost[i][j-],maxCost[a][j-]);
  13. }
  14. }
  15. }
  16. }
  17. int query(int p,int q){
  18. int log;
  19. if (L[p]<L[q]) swap(p,q);
  20. for (log=;(<<log)<=L[p];log++);log--;
  21. int ans=-INF;
  22. for (int i=log;i>=;i--){
  23. if (L[p]-(<<i)>=L[q]){
  24. ans=max(ans,maxCost[p][i]);
  25. p=anc[p][i];
  26. }
  27. }
  28. if (p==q) return ans;
  29. for (int i=log;i>=;i--){
  30. if (anc[p][i]!=-&&anc[p][i]!=anc[q][i]){
  31. ans=max(ans,maxCost[p][i]);
  32. p=anc[p][i];
  33. ans=max(ans,maxCost[q][i]);
  34. q=anc[q][i];
  35. }
  36. }
  37. ans=max(ans,cost[p]);
  38. ans=max(ans,cost[q]);
  39. return ans;
  40. }

【转】最近公共祖先(LCA)的更多相关文章

  1. Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)

    Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...

  2. POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)

    POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...

  3. POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)

    POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...

  4. [模板] 最近公共祖先/lca

    简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...

  5. 【lhyaaa】最近公共祖先LCA——倍增!!!

    高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...

  6. POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)

    Tarjan算法的详细介绍,请戳: http://www.cnblogs.com/chenxiwenruo/p/3529533.html #include <iostream> #incl ...

  7. 【Leetcode】查找二叉树中任意结点的最近公共祖先(LCA问题)

    寻找最近公共祖先,示例如下: 1 /           \ 2           3 /    \        /    \ 4    5      6    7 /    \          ...

  8. 最近公共祖先LCA(Tarjan算法)的思考和算法实现

    LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...

  9. 查找最近公共祖先(LCA)

    一.问题 求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树).最近公共祖先简称LCA(Lowest Common Ancestor).例如,如下图一棵普通的二叉树. 结点3和结点4的最近公共 ...

  10. 最近公共祖先(LCA)的三种求解方法

    转载来自:https://blog.andrewei.info/2015/10/08/e6-9c-80-e8-bf-91-e5-85-ac-e5-85-b1-e7-a5-96-e5-85-88lca- ...

随机推荐

  1. javascript创建对象的一些方式

    通过创建一个Object实例 var person = new Object(); person.name = "zhouquan"; person.age = 21; perso ...

  2. STM32F103之DMA

    一.背景: 需要使用STM32的DAC,例程代码中用了DMA,对DMA之前没有实际操作过,也很早就想知道DMA到底是什么,因此,看了一下午手册,代码和网上的资料,便有了此篇文章,做个记录. 二.正文: ...

  3. css浏览器兼容问题

    https://www.douban.com/group/topic/4629864/

  4. 为C#自定义控件添加自定义事件

    这里的自定义控件是由普通控件组合而成的. 希望事件响应代码推迟到使用自定义控件的窗体里写. 步骤一:新建一个用户控件,放两个按钮,Tag分别是btn1,btn2. 这两个按钮的共用单击事件处理代码如下 ...

  5. Android 一些基本组件的使用

    Dialog 基本用法 ,带自定义view AlertDialog dialog = new AlertDialog.Builder(context).setTitle("写入信息" ...

  6. jQuery解析AJAX返回的html数据时碰到的问题与解决

    $.ajax({ type : "post", url : "<%=request.getContextPath()%>/ce/articledetail/m ...

  7. Java连接mysql数据库并插入中文数据显示乱码

    连接数据库设置编码 jdbc:mysql://地址:3306/数据库名?characterEncoding=utf8

  8. JavaScript——理解闭包及作用

    js是一个函数级语言,变量的作用域是: 内部可以访问内部,内部可以访问外部,外部不能访问内部. 如果要在外部,访问函数内部的变量,就要用到闭包.闭包就是指访问到了本不该访问的变量. 闭包作用1:实现封 ...

  9. css3美化复选框checkbox

     两种美化效果如下图: 代码(html) <div id="main"> <h2 class="top_title">使用CSS3美化复 ...

  10. C#实现K-MEDOIDS聚类算法

    1.任意选取K个对象作为初始聚类中心(O1,O2,…Oi…Ok). 2)将余下的对象分到各个类中去(该对象与哪一个聚类中心最近就被分配到哪一个聚类簇中): 3)对于每个类(Oi)中,顺序选取一个Or, ...