算法学习笔记【1】| LCA(最近公共祖先)
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(最近公共祖先)的更多相关文章
- 算法学习笔记(5): 最近公共祖先(LCA)
最近公共祖先(LCA) 目录 最近公共祖先(LCA) 定义 求法 方法一:树上倍增 朴素算法 复杂度分析 方法二:dfs序与ST表 初始化与查询 复杂度分析 方法三:树链剖分 DFS序 性质 重链 重 ...
- Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)
Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...
- LCA近期公共祖先
LCA近期公共祖先 该分析转之:http://kmplayer.iteye.com/blog/604518 1,并查集+dfs 对整个树进行深度优先遍历.并在遍历的过程中不断地把一些眼下可能查询到的而 ...
- LCA 近期公共祖先 小结
LCA 近期公共祖先 小结 以poj 1330为例.对LCA的3种经常使用的算法进行介绍,分别为 1. 离线tarjan 2. 基于倍增法的LCA 3. 基于RMQ的LCA 1. 离线tarjan / ...
- lca 最近公共祖先
http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ...
- C / C++算法学习笔记(8)-SHELL排序
原始地址:C / C++算法学习笔记(8)-SHELL排序 基本思想 先取一个小于n的整数d1作为第一个增量(gap),把文件的全部记录分成d1个组.所有距离为dl的倍数的记录放在同一个组中.先在各组 ...
- Manacher算法学习笔记 | LeetCode#5
Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...
- LCA(最近公共祖先)模板
Tarjan版本 /* gyt Live up to every day */ #pragma comment(linker,"/STACK:1024000000,1024000000&qu ...
- CodeVs.1036 商务旅行 ( LCA 最近公共祖先 )
CodeVs.1036 商务旅行 ( LCA 最近公共祖先 ) 题意分析 某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间. 假设有N个城镇,首都编号为1,商人从 ...
- Johnson算法学习笔记
\(Johnson\)算法学习笔记. 在最短路的学习中,我们曾学习了三种最短路的算法,\(Bellman-Ford\)算法及其队列优化\(SPFA\)算法,\(Dijkstra\)算法.这些算法可以快 ...
随机推荐
- DFS算法模板(2488:A Knight's Journey)
DFS算法(C++版本) 题目一: 链接:http://bailian.openjudge.cn/practice/2488/ 解析思路: 骑士找路就是基本的DFS,用递归不断找到合适的路,找不到就回 ...
- QT - Day 5
1 event事件 用途:用于事件的分发 也可以做拦截操作,不建议 bool event( QEvent * e); 返回值 如果是true 代表用户处理这个事件,不向下分发了 e->ty ...
- 【Android 逆向】【攻防世界】APK逆向
1. apk安装到手机,提示输入flag 2. jadx打开apk 定位到checkSN方法 public boolean checkSN(String userName, String sn) { ...
- 项目实战:Qt终端命令模拟工具 v1.0.0(实时获取命令行输出,执行指令,模拟ctrl+c中止操作)
需求 在Qt软件中实现部分终端控制命令行功能,使软件内可以又好的模拟终端控制,提升软件整体契合度. Demo演示 运行包下载地址: CSDNf粉丝0积分下载:https: ...
- 【开发工具】Linux 服务器 Shell 脚本简单入门
记录一下学习Shell编程的关键知识点,使用最通俗简洁的语句,让阅读者能快速上手Shell脚本的编写 1.什么是Shell? Shell是一种常用于服务器运维的脚本语言.众所周知,脚本语言不需要编译器 ...
- 【C++ OOP 01】封装
封装 封装的意义 封装是C++面向对象三大特性之一 封装的意义: 将属性和行为作为一个整体,表现生活中的事物 将属性和行为加以权限控制 封装意义一 在设计类的时候,属性和行为写在一起,表现事物 语 ...
- 读 NebulaGraph源码 | 查询语句 LOOKUP 的一生
本文由社区用户 Milittle 供稿 LOOKUP 是图数据库 NebulaGraph 的一个查询语句.它依赖索引,可以查询点或者边的信息.在本文,我将着重从源码的角度解析一下 LOOKUP 语句的 ...
- 问答:C程序为何for循环和while循环无法相互替代?
百鸡百钱问题: C代码: include <stdio.h> main() { int cock, hen, chicken; for(cock=0;cock<=20;cock++) ...
- 2022_vue3笔记
由于公司项目有vue2.5,自己电脑又要3.2,总不可能总是安装删除环境,这儿使用安装nvm版本管理 安装node前配置一下镜像地址 node_mirror: https://npm.taobao.o ...
- SSH原理与实践(三)安装和使用
主页 个人微信公众号:密码应用技术实战 个人博客园首页:https://www.cnblogs.com/informatics/ 引言 在之前SSH原理与实践系列文章中,我们主要讲解了SSH协议的原理 ...