LCA(最近公共祖先)

Part 1:逐步上跳

假设u,v是所求的两点,先把深度大的点逐步上移直到深度相同。 然后两者同时上移,直到第一次遇到相同的点为止。

时间复杂度: O(n)<script type="math/tex;mode=inline" id="MathJax-Element-1">O(n)</script>O(n)

Part 2:倍增

考虑优化跳的过程。

定义anc[k][i],表示i向上第 2k<script type="math/tex;mode=inline" id="MathJax-Element-2">2^k </script>2^k 个祖先。
anc可以按照k为阶段动态规划得出,初始化为:anc[0][i]=fa[i];

//Author: Velvet on Luogu(uid=443675)
void init(){
F(k,1,18){
F(i,1,n){
anc[k][i]=anc[k-1][anc[k-1][i]];
}
}
}
//Author: Velvet on Luogu(uid=443675)
int LCA(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
dF(i,18,0){
if(dep[anc[i][u]]>=dep[v]){
u=anc[i][u];
}
}
if(u==v) return u;
dF(i,18,0){
if(anc[i][u]!=anc[i][v]){
u=anc[i][u];v=anc[i][v];
}
}
return anc[0][u];
}

if(u==v) return u;,这句话很重要,没有的话会导致答案变成LCA的祖先。

至于anc可能超出树的大小的问题,可以这样理解:
如果anc[i][u]超出树的范围,dep[anc[i][u]]=0,必然不会赋值给u。
下面也是一样,anc都是0,不会赋值,保证答案的合法。

Part 3:Tarjan(离线算法)

Tarjan 是基于并查集的。

倍增法优化了向上跳这一步骤,Tarjan 算法则在第一次遍历整棵树的时候得到所有答案,因此需要先存储下每个询问,故该算法为离线算法。

假设 x,y 的LCA为 u。那么x,y必须是u的不同子树中的节点。
我们把每一次操作完后的子树和其父亲节点合并,这里使用并查集。
然后对于任意一个其他子树中的被提问节点,判断其询问的另一个节点有无访问记录,若是,记录答案为其并查集树根。

//Author: Velvet on Luogu(uid=443675)
void dfs(int now){
fa[now]=now;//并查集初始化
vis[now]=1;//访问记录
for(auto i:G[now]){
if(vis[i]) continue;
dfs(i);
fa[i]=now;//合并
}
for(auto i:Q[now]){//询问列表(i.first是另一个询问,i.second是询问的顺序
if(!vis[i.first]) continue;
ans[i.second]=find(i.first);
}
}

Part 4:树剖

先重链剖分这棵树。每次求LCA就不断跳重链,直到u,v在同一条重链上,答案就是u,v中深度小的。
向上跳重链时需要先跳所在重链顶端深度较大的那个。

//Author: Velvet on Luogu(uid=443675)
const int N=500005,M=(N<<1),inf=0x3f3f3f3f;
int n,m,s,siz[N],fa[N],hson[N],top[N],dep[N];
vector<int> G[N];
void dfs1(int now,int father){
siz[now] = 1;
for(auto i:G[now]){
if(i == father) continue;
dep[i] = dep[now] + 1;
dfs1(i,now);
siz[now] += siz[i];
fa[i] = now;
if(siz[hson[now]]<siz[i])
hson[now] = i;
}
}
void dfs2(int now,int t){
top[now] = t;
if(hson[now])
dfs2(hson[now],t);
for(auto i:G[now]){
if(i == hson[now]||i == fa[now]) continue;
dfs2(i,i);
}
}
int LCA(int u,int v){
while(top[u] != top[v]){
if(dep[top[u]]<dep[top[v]]) v=fa[top[v]];
else u = fa[top[u]];
}
return dep[u] > dep[v] ? v : u;
}
int main(){
ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>m>>s;
F(i,1,n-1){
int u,v;
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dep[s] = 1;
dfs1(s,-1);
dfs2(s,s);
F(i,1,m){
int u,v;
cin>>u>>v;
cout<<LCA(u,v)<<endl;
}
return 0;
}

模板题(on Luogu):SP14932,P3379

算法学习笔记【1】| LCA(最近公共祖先)的更多相关文章

  1. 算法学习笔记(5): 最近公共祖先(LCA)

    最近公共祖先(LCA) 目录 最近公共祖先(LCA) 定义 求法 方法一:树上倍增 朴素算法 复杂度分析 方法二:dfs序与ST表 初始化与查询 复杂度分析 方法三:树链剖分 DFS序 性质 重链 重 ...

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

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

  3. LCA近期公共祖先

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

  4. LCA 近期公共祖先 小结

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

  5. lca 最近公共祖先

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

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

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

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

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

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

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

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

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

  10. Johnson算法学习笔记

    \(Johnson\)算法学习笔记. 在最短路的学习中,我们曾学习了三种最短路的算法,\(Bellman-Ford\)算法及其队列优化\(SPFA\)算法,\(Dijkstra\)算法.这些算法可以快 ...

随机推荐

  1. centos7搭建postgresql主从(主备)架构

    本篇介绍如何在centos7系统搭建一个postgresql主备集群实现最近的HA(高可用)架构.后续更高级的HA模式都是基于这个最基本的主备搭建. 节点规划 ip 主机名 用途 192.168.18 ...

  2. 《系列一》-- 4、xml配置文件解析之[默认]命名空间[标签]的解析

    阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证.全系列文章基于 spring 源码 5.x 版本. Spring源码阅读系列--全局目录.md ...

  3. win32编辑控件字体

    每次到用的时候就各种查资料,我这人记性又不好,遂记录下来: 普通的编辑控件: 创建:HWND hText = CreateWindowW(L"EDIT", L"enter ...

  4. win32 - 使用VerQueryValue获得应用程序的名称

    比如: Google Chrome: 类似于任务管理器中显示名字,见下图 那么我们就需要使用VerQueryValue, 从指定的版本信息资源中检索指定的版本信息.若要检索适当的资源,在调用VerQu ...

  5. Ansible原理和安装

    目录 Ansible Ansible简介 Ansible的特性 Ansible的基本组件 Ansible安装(rhel8/rhel9) 1. rhel8安装 1.1 配置epel源 1.2 安装ans ...

  6. 字符串,format格式化及列表的相关进阶操作---day07

    1.字符串相关操作 (1)字符串的拼接 (2)字符串的重复 (3)字符串跨行拼接 (4)字符串的索引 (5)字符串的切片:[开始索引:结束索引:步长] 2.字符串的格式化format (1)顺序传参 ...

  7. pyqt5中通过pycharm配置designer(win和mac都适用,修改下designer目录路径即可)

    安装 pip install PyQt5 -i https://pypi.douban.com/simple pip install PyQt5-tools -i https://pypi.douba ...

  8. 【LeetCode回溯算法#05】分割回文串(复习双指针判断回文以及substr函数使用记录)

    分割回文串 力扣题目链接 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 .返回 s 所有可能的分割方案. 回文串 是正着读和反着读都一样的字符串. 示例 1: 输入:s = ...

  9. 矩池云如何自定义端口,访问自己的web项目

    本文将给您介绍如何在矩池云租用服务器的时候自定义端口,并将您的 web 项目部署到自定义端口,最后实现在本地通过自定义端口对应链接访问服务. 上传代码和数据 首先,您需要将本地的项目代码和数据上传到矩 ...

  10. 读 NebulaGraph源码 | 查询语句 LOOKUP 的一生

    本文由社区用户 Milittle 供稿 LOOKUP 是图数据库 NebulaGraph 的一个查询语句.它依赖索引,可以查询点或者边的信息.在本文,我将着重从源码的角度解析一下 LOOKUP 语句的 ...