最近公共祖先 LCA 倍增写法

LCA的倍增主要由三个重要的过程组成

预处理lg数组

DFS求fa depth

倍增节点

观看以下内容前建议先把完整代码大致纵览一遍,有利于理解各个函数的意义

倍增思想

暴力解决LCA是通过 x 和 y 一个一个的往上跳

而倍增的思想是希望节点能够一次性尽可能的多跳

并且可以通过一个有序数列来控制跳的层数

1 2 4 8 16 32 ...

\(2^0\) \(2^1\) \(2^2\) \(2^3\) \(2^4\) \(2^5\) ...

(可以有机联想10进制到2进制的转化)

比如: 当前需要跳35层

35 - 32 = 3

3 - 2 = 1

1 - 1 = 0

这样就将原本需要跳35次压缩到只需要跳3次

那如何知道应该跳几次呢?

这时候就要通过lg数组来实现

预处理lg数组

lg数组的含义: \(lg[i]\) 代表深度为i的节点一次性可以往上最多跳 2(lg[i]-1) 个节点

在 Read() 中,我们可以找到如下代码:

	for(int i = 1;i <= n;i++){
lg[i] = lg[i-1] + (1 << lg[i-1] == i);
}

为了理解这段话的意思 我们先把这个处理后的数组 lg[0 - 100] 打印出来:

0 1 2 2 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7

比如: 当预期要跳的深度为35 那就看 lg[35] = 6

则需要跳的次数为 26-1 = 32

所以最多跳32层

其余也是同理

DFS求fa depth

Code

void dfs(int u,int f){
fa[u][0] = f;
depth[u] = depth[f] + 1;
for(int i = 1;i <= lg[depth[u]];i++)
fa[u][i] = fa[fa[u][i-1]][i-1];
for(int i = head[u];i;i = e[i].u){
if(e[i].v != f) dfs(e[i].v,u);
}
}

求depth

我们都知道DFS是从上往下搜的

可以通过父节点的depth+1来得到当前节点的depth

因此处理depth只需要将父节点的值加1即可:

	depth[u] = depth[f] + 1;

求fa

要求fa数组要先搞懂其含义:\(fa[i][j]\) 指 节点 \(i\) 的 \(2^j\)级祖先

由于 \(2^j\) = 2j-1 + 2j-1

所以 \(i\) 的 2j-1 级祖先的 \(j-1\) 级祖先就是 \(i\) 的 \(j\) 级祖先:

	fa[u][i] = fa[fa[u][i-1]][i-1];

既然要通过前面的fa求后面的fa

那必须将\(fa[i][0]\)求出 既 \(i\) 的父节点:

	fa[u][0] = f;

倍增节点

Code

int LCA(int x,int y){
if(depth[x] < depth[y]) swap(x,y);//让 节点x 作为较深的节点
while(depth[x] > depth[y]){//把 x 和 y 放至相同深度
x = fa[x][lg[depth[x]-depth[y]] - 1];
}
if(x == y) return y;/*
如果 x 被拉到 y 上
说明 y 就是 x 的祖先
因此直接返回 y
*/
for(int k = lg[depth[x]] - 1;k >= 0;k--){
if(fa[x][k] != fa[y][k])//如果 x 和 y 没有相遇 则说明可以倍增到这个位置
x = fa[x][k],y = fa[y][k];
}
return fa[x][0];//返回 x 的父亲节点
}

注释中写道 如果 x 和 y 没有相遇 则说明可以倍增到这个位置

这是什么意思?

我们不妨设想如果没有这个if()

直接相遇就break;

那就会发生非最近公共祖先的问题(就是倍增过头了)

因此一直倍增却不允许相遇 就会让他们停在LCA的下一层

这一部分比较简单 所以就直接把注释打在代码上了

完整代码

//P3379 注释版
#include<bits/stdc++.h>
#define maxn 500010
using namespace std; int head[maxn],cnt;
struct tree{
int u,v;
tree(int a = 0,int b = 0){
u = head[a];
v = b;
}
}e[maxn << 1];
void add(int u,int v){
e[++cnt]=tree(u,v);
head[u] = cnt;
e[++cnt]=tree(v,u);
head[v] = cnt;
}//邻接表 int depth[maxn],fa[maxn][22],lg[maxn];
int m,n,s;
/*
定义变量
depth[i] 节点i的深度
fa[i][j] 节点i的2^J级祖先
lg[i] 辅助参数
*/ void Read(){//输入
cin >> n >> m >> s;
int x,y;
for(int i = 1;i < n;i++){
cin >> x >> y;
add(x,y);
}
for(int i = 1;i <= n;i++){
lg[i] = lg[i-1] + (1 << lg[i-1] == i);
}/*
常数优化
lg[i]代表深度为i的节点可以往上最多跳2^(lg[i] - 1)个节点
*/
} void dfs(int u,int f){
fa[u][0] = f;
depth[u] = depth[f] + 1;
for(int i = 1;i <= lg[depth[u]];i++)
fa[u][i] = fa[fa[u][i-1]][i-1];
for(int i = head[u];i;i = e[i].u){
if(e[i].v != f) dfs(e[i].v,u);
}
}/*
DFS
可以求出 fa depth
*/ int LCA(int x,int y){
if(depth[x] < depth[y]) swap(x,y);//让 节点x 作为较深的节点
while(depth[x] > depth[y]){//把 x 和 y 放至相同深度
x = fa[x][lg[depth[x]-depth[y]] - 1];
}
if(x == y) return y;/*
如果 x 被拉到 y 上
说明 y 就是 x 的祖先
因此直接返回 y
*/
for(int k = lg[depth[x]] - 1;k >= 0;k--){
if(fa[x][k] != fa[y][k])//如果 x 和 y 没有相遇 则说明可以倍增到这个位置
x = fa[x][k],y = fa[y][k];
}
return fa[x][0];//返回 x 的父亲节点
} int main(){
Read();
dfs(s,0);
for(int i = 1;i <= m;i++){
int x,y;
cin >> x >> y;
cout << LCA(x,y) <<endl;
}
return 0;
}

[C++]P3379 LCA 最近公共祖先的更多相关文章

  1. lca 最近公共祖先

    http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ...

  2. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)

    Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...

  3. LCA(最近公共祖先)模板

    Tarjan版本 /* gyt Live up to every day */ #pragma comment(linker,"/STACK:1024000000,1024000000&qu ...

  4. CodeVs.1036 商务旅行 ( LCA 最近公共祖先 )

    CodeVs.1036 商务旅行 ( LCA 最近公共祖先 ) 题意分析 某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间. 假设有N个城镇,首都编号为1,商人从 ...

  5. LCA近期公共祖先

    LCA近期公共祖先 该分析转之:http://kmplayer.iteye.com/blog/604518 1,并查集+dfs 对整个树进行深度优先遍历.并在遍历的过程中不断地把一些眼下可能查询到的而 ...

  6. LCA 近期公共祖先 小结

    LCA 近期公共祖先 小结 以poj 1330为例.对LCA的3种经常使用的算法进行介绍,分别为 1. 离线tarjan 2. 基于倍增法的LCA 3. 基于RMQ的LCA 1. 离线tarjan / ...

  7. LCA 最近公共祖先 (笔记、模板)

    求lca的方法大体有三种: 1.dfs+RMQ(线段树 ST表什么的) 在线 2.倍增 在线 3.tarjan 离线 ps:离线:所有查询全输入后一次解决 在线:有一个查询输出一次 以下模板题为 洛谷 ...

  8. LCA最近公共祖先---倍增法笔记

    先暂时把模板写出来,A几道题再来补充 此模板也是洛谷上的一道模板题 P3379 [模板]最近公共祖先(LCA) #pragma GCC optimize(2) //o2优化 #include < ...

  9. 【图论算法】LCA最近公共祖先问题

    LCA模板题https://www.luogu.com.cn/problem/P3379题意理解 对于有根树T的两个结点u.v,最近公共祖先LCA(u,v)表示一个结点x,满足x是u.v的祖先且x的深 ...

  10. LCA最近公共祖先 ST+RMQ在线算法

    对于一类题目,是一棵树或者森林,有多次查询,求2点间的距离,可以用LCA来解决.     这一类的问题有2中解决方法.第一种就是tarjan的离线算法,还有一中是基于ST算法的在线算法.复杂度都是O( ...

随机推荐

  1. 如何在 Ubuntu 20.04 上安装 KVM

    如何在 Ubuntu 20.04 上安装 KVM 遇到两个问题: 没有创建 /etc/qemu/bridge.conf 文件 ERROR internal error: /usr/lib/qemu/q ...

  2. AcWing 4798. 打怪兽题解

    可以从 \(1\) 枚举到 \(n\) 表示要打多少个怪兽. 因为你要打 \(t\) 个怪兽,并不管顺序,所以我们可以对 \([1, t]\) 这一段进行排序,然后计算 \(a[t], a[t - 2 ...

  3. 文心一言 VS 讯飞星火 VS chatgpt (71)-- 算法导论7.1 1题

    参照图 7-1的方法,说明 PARTITION在数组 A=(13,19,9,5,12,8,7,4,21,2,6,11)上的操作过程. 文心一言: PARTITION 是一种常见的快速排序算法,其目的是 ...

  4. vscode c++食用指南

    准备 配置环境为机房的 win10. 首先你需要下载 vscode. 可以从官网下载:https://code.visualstudio.com/Download 配置编译c++ 下载完之后安装好,界 ...

  5. [ABC145F] Laminate

    2023-02-25 题目 题目传送门 翻译 翻译 难度&重要性(1~10):6 题目来源 AtCoder 题目算法 dp 解题思路 引子:积木大赛 可以发现当 \(k=1\) 时,就是积木大 ...

  6. grinder使用入门

    安装配置 自动安装脚本编写 移步我的github下载脚本,只要修改下配置信息,即可完成安装 环境变量设置:

  7. Code Llama:Llama 2 学会写代码了!

    引言 Code Llama 是为代码类任务而生的一组最先进的.开放的 Llama 2 模型,我们很高兴能将其集成入 Hugging Face 生态系统!Code Llama 使用与 Llama 2 相 ...

  8. webapi开发框架实践

    项目链接以及目录结构 liuzhixin405/efcore-template (github.com) 这是一个纯webapi的开发框架. 1.支持的orm有efcore6.dapper,可以灵活切 ...

  9. 4-MySQL数据库的常用操作

    在MySQL数据库中,增删改查操作是指对数据进行添加.删除.查询和修改的操作.这些操作在数据库管理和维护中非常重要,可以帮助数据库管理员和开发人员有效地管理数据和实现各种复杂的数据处理需求. 添加数据 ...

  10. 音频格式轻松转 - foobar2000

    一.foobar2000简介 foobar2000 是一款免费的专业级别音频解码播放器,支持的诸多音频格式,可加载附加组件扩展更多支持. 除了解码以外,可轻松实现对音频格式的转换,支持几乎所有主流格式 ...