当我们处理树上点与点关系的问题时(例如,最简单的,树上两点的距离),常常需要获知树上两点的最近公共祖先(Lowest Common Ancestor,LCA)。如下图所示:

2号点是7号点和9号点的最近公共祖先

我们先来讨论朴素的做法。

首先进行一趟dfs,求出每个点的深度:

int dep[MAXN];
bool vis[MAXN];
void dfs(int cur, int fath = 0)
{
if (vis[cur])
return;
vis[cur] = true;
dep[cur] = dep[fath] + 1; // 每个点的深度等于父节点的深度+1
for (int eg = head[cur]; eg != 0; eg = edges[eg].next)
dfs(edges[eg].to, cur);
}

现在A点的深度比B点深,所以我们先让B点往上“爬”,爬到与A点深度相等为止。然后A点和B点再一起往上爬,直到两点相遇,那一点即为LCA:

这样下来,每次查询LCA的最坏时间复杂度是 的。


有时候,我们需要进行很多次查询,这时朴素的 复杂度就不够用了。我们考虑空间换时间的倍增算法。

倍增的思想直观体现就在 ST表 中提及过。我们用一个数组fa[i][k]存储 号点的 级祖先。(父节点为1级祖先,祖父结点为2级祖先……以此类推)

那么这可以在dfs途中动态规划得出:

// 在dfs中...
fa[cur][0] = fath;
for (int i = 1; i <= Log2[dep[cur]]; ++i) // Log2的预处理参见ST表的笔记
fa[cur][i] = fa[fa[cur][i - 1]][i - 1]; // 这个DP也参见ST表的笔记

这样,往上爬的次数可以被大大缩短(现在变成“跳”了)。

首先还是先让两点深度相等:

if (dep[a] > dep[b]) // 不妨设a的深度小于等于b
swap(a, b);
while (dep[a] != dep[b]) // 跳到深度相等为止
b = fa[b][Log2[dep[b] - dep[a]]]; // b不断往上跳

例如,a和b本来相差22的深度,现在b不用往上爬22次,只需要依次跳16、4、2个单位,3次便能达到与a相同的距离。

两者深度相等后,如果两个点已经相遇,那么问题就得以解决。如果尚未相遇,我们再让它们一起往上跳。问题在于,如何确定每次要跳多少?正面解决也许不太容易,我们逆向思考:如何在a、b不相遇的情况下跳到尽可能高的位置?如果找到了这个位置,它的父亲就是LCA了。

说来也简单,从可能跳的最大步数Log2[dep[a]](这样至多跳到0号点,不会越界)开始,不断减半步数(不用多次循环):

for (int k = Log2[dep[a]]; k >= 0; k--)
if (fa[a][k] != fa[b][k])
a = fa[a][k], b = fa[b][k];

以刚刚那棵树为例,先尝试Log2[4]=2,A、B点的 级祖先都是0(图中未画出),所以不跳。然后尝试1,A、B的 祖先都是2,也不跳。最后尝试0,A、B的1级祖先分别是4和5,跳。结束。

这样下来,再往上一格所得到的2号点就是所求的最近公共祖先。


主要代码如下:

int Log2[MAXN], fa[MAXN][20], dep[MAXN]; // fa的第二维大小不应小于log2(MAXN)
bool vis[MAXN];
void dfs(int cur, int fath = 0)
{
if (vis[cur])
return;
vis[cur] = true;
dep[cur] = dep[fath] + 1;
fa[cur][0] = fath;
for (int i = 1; i <= Log2[dep[cur]]; ++i)
fa[cur][i] = fa[fa[cur][i - 1]][i - 1];
for (int eg = head[cur]; eg != 0; eg = edges[eg].next)
dfs(edges[eg].to, cur);
}
int lca(int a, int b)
{
if (dep[a] > dep[b])
swap(a, b);
while (dep[a] != dep[b])
b = fa[b][Log2[dep[b] - dep[a]]];
if (a == b)
return a;
for (int k = Log2[dep[a]]; k >= 0; k--)
if (fa[a][k] != fa[b][k])
a = fa[a][k], b = fa[b][k];
return fa[a][0];
}
int main()
{
// ...
for (int i = 2; i <= n; ++i)
Log2[i] = Log2[i / 2] + 1;
// ...
dfs(s); // 无根树可以随意选一点为根
// ...
return 0;
}

至于树上两点 的距离,有公式 (很好推)。 预处理, 查询,空间复杂度为

当然,以上都是针对无权树的,如果有权值,可以额外记录一下每个点到根的距离,然后用几乎相同的公式求出。

算法学习笔记:最近公共祖先(LCA问题)的更多相关文章

  1. 学习笔记--最近公共祖先(LCA)的几种求法

    前言: 给定一个有根树,若节点\(z\)是两节点\(x,y\)所有公共祖先深度最大的那一个,则称\(z\)是\(x,y\)的最近公共祖先(\(Least Common Ancestors\)),简称\ ...

  2. [一本通学习笔记] 最近公共祖先LCA

    本节内容过于暴力没什么好说的.借着这个专题改掉写倍增的陋习,虽然写链剖代码长了点不过常数小还是很香. 10130. 「一本通 4.4 例 1」点的距离 #include <bits/stdc++ ...

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

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

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

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

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

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

  6. [知识点]最近公共祖先LCA

    UPDATE(20180822):重写部分代码. 1.前言 最近公共祖先(LCA),作为树上问题,应用非常广泛,而求解的方式也非常多,复杂度各有不同,这里对几种常用的方法汇一下总. 2.基本概念和暴力 ...

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

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

  8. C / C++算法学习笔记(8)-SHELL排序

    原始地址:C / C++算法学习笔记(8)-SHELL排序 基本思想 先取一个小于n的整数d1作为第一个增量(gap),把文件的全部记录分成d1个组.所有距离为dl的倍数的记录放在同一个组中.先在各组 ...

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

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

  10. Manacher算法学习笔记 | LeetCode#5

    Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...

随机推荐

  1. 02-Python运算符

    一.简介 以10 - 5为例,‘10 - 5’叫做表达式,表达式可以分解成运算符和操作数.整数10和5被称为操作数.‘-’称为运算符. 二.算术运算符 运算符 描述 示例 结果 + 加 - 两个对象相 ...

  2. 绘图和可视化知识图谱-《利用Python进行数据分析》

    所有内容整理自<利用Python进行数据分析>,使用MindMaster Pro 7.3制作,emmx格式,源文件已经上传Github,需要的同学转左上角自行下载或者右击保存图片. 其他章 ...

  3. Python Ethical Hacking - BACKDOORS(7)

    Handling Errors: If the client or server crashes, the connection will be lost. Backdoor crashes if: ...

  4. 题解 CF1359B 【New Theatre Square】

    题意 有一个 n×m 的广场,其中一部分要铺地砖,地砖有两种, 1 × 1 和 1×2 的,后者只能横着铺,其中, 1 × 1的单价为 x , 1 × 2 的单价为 y , 输入这个广场," ...

  5. java大数据最全课程学习笔记(6)--MapReduce精通(二)--MapReduce框架原理

    目前CSDN,博客园,简书同步发表中,更多精彩欢迎访问我的gitee pages 目录 MapReduce精通(二) MapReduce框架原理 MapReduce工作流程 InputFormat数据 ...

  6. SAS X option

    1. SAS X选项就是调用DOS命令. 例子: option noxwait;/*黑窗口执行完命令后自动关闭*/ %let path =.; %let filter=*.lst; X “ dir & ...

  7. MySQL 8.0.20 安装教程图文详解(windows 64位)

    MySQL 8.0.20 安装教程图文详解(windows 64位)  更新时间:2020年05月09日 15:09:04   转载 作者:瘦肉粥不加糖     这篇文章主要介绍了MySQL 8.0. ...

  8. 事件流和初识Jquery

    一.事件流 定义: 多个彼此嵌套元素,他们拥有相同的事件,最内部元素事件被触发后,外边多个元素的同类型事件也会被触发,多个元素他们同类型事件同时执行的效果称为“事件流”. 事件对象获取: 获得: ①主 ...

  9. 深入探究JVM之垃圾回收算法实现细节

    @ 目录 前言 垃圾回收算法实现细节 根节点枚举 安全点 安全区域 记忆集和卡表 写屏障 并发的可达性分析 低延迟GC Shenandoah ZGC 总结 前言 本篇紧接上文,主要讲解垃圾回收算法的实 ...

  10. php iamp 接收邮件,收取邮件,获取邮件列表

    每次想写的时候吧,提笔忘字.等到再次使用,又得想半天,,,,,好尴尬. 这次一边做一边写. 心得,程序员从菜鸟往老鸟转变的重要一步,学英语,看文档,在此我万分感谢鸟哥,,,,没他php哪有官方的中文注 ...