【模板】最近公共祖先:LCA算法
LCA最近公共祖先
要求 \ 给出一个树和他的根节点\text{root} \quad给出Q个询问 回答\text {LCA}(a,b)
\end{align}
\]
给出一个表格
| 最近公共祖先 | 朴素算法 | 倍增算法 | Tarjan算法 | 树链剖分 |
|---|---|---|---|---|
| 数据结构 | fa[u],dep[u] | fa[u] [v], dep[u] | query[u], ans[i], vis[u] | fa[u], dep[u], size_[u], son[u], top[u] |
| 算法 | 暴力 | 倍增法 | 并查集 | 重链剖分 |
| 做法 | 暴力一次跳跃 | 深搜打表,跳跃查询 | 深搜,回时指父,离时搜根 | 两遍深搜打表,跳跃查询 |
| 时间复杂度 | O(n^2) | O(n + mlogn) | O(n+m) | O(n + mlogn) |
朴素版本
& \mathrm{LCA}朴素版\quad查询复杂度:O(n) \\
& 一次一次往上跳\\
& 让u是更深的节点\quad然后把u跳到和v一样的高度\\
& 然后u,v同时往上跳 当u==v时就是答案
\end{align*}
\]
#include <cstdio>
#include <queue>
#include <deque>
#include <stack>
#include <map>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
#define ep emplace_back
#define lld long long
#define ios std::ios::sync_with_stdio(false);std::cin.tie(0);
#define vec vector
const int N = 2e6+9;
const int INF = 0x7FFFFFFF; //2147483647
const int inf1 = 0x3f3f3f3f; //1061109567
const int inf2 = 0x7f7f7f7f; //2139062143 memset赋值用
using namespace std;
int head[N],idx=0;
struct node{
int to,val,next;
};
node e[N<<1];
int fa[N];
void add(int u ,int v, int val){
e[idx] = {v,val,head[u]};
head[u] = idx++;
}
int a[N],b[N];
int n,root,Q;
int depth[N],max_dep=-1;
int pa[N];
vec<int>child[N];
void bd(){
cin>>n>>Q>>root;
memset(head,-1,sizeof(head));
for(int i=1;i<=n;++i){
int u,v;
cin>>u>>v;
add(u,v,0);
add(v,u,0);
//树是双向边
}
}
void dfs(int u){
for(int i=head[u] ; i!=-1 ; i=e[i].next){
int v = e[i].to;
if(v == fa[u]) continue;
fa[v] = u;
depth[v] = depth[u]+1;
max_dep = max(max_dep,depth[v]);
dfs(v);
}
}
int LCA(int u,int v){
//朴素版LCA 一次一次向上跳
if(depth[u] < depth[v])
swap(u,v);
//默认最深的点是u
while(depth[u] != depth[v]){
u = fa[u];
//先把u往上跳
//u的深度和v一样
}
while( u != v){
u=fa[u];
v=fa[v];
//u,v深度一样之后只需要把u v一起往上跳跃
//当u==v 时 这个时候的u==v ==LCA(u,v)
}
return u;
}
int main(){
ios;
bd();
depth[root] = 0;
fa[root] = -1;
dfs(root);
for(int i=1;i<=Q;++i){
int a,b;
cin>>a>>b;
cout<<LCA(a,b)<<"\n";
}
return 0;
}
倍增加速算法版本
& 在线版本\to倍增法\quad n = \sum_{k=0} ^{\log_2n}2^{k}\\
& 不断分解查询的步骤\\
& 即2^k = 2^{k-1}+2^{k-1}\quad \\
&令u=\mathrm{anc}(i,j-1) ,\mathrm{anc}(i,j) = \mathrm{anc}(u,j-1)\\
&然后通过\text{dfs}记录每个点的\text{depth}和他的父亲节点\\
&需要注意的问题是可能会跳过头,因此需要跳到那个最离v最近的地方\\
&然后同时把u,v向上跳 u,v的\text{anc}(u,0)就是\text{LCA}(u,v)
\end{align}
\]
#include <cstdio>
#include <queue>
#include <deque>
#include <stack>
#include <map>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
#define ep emplace_back
#define lld long long
#define ios std::ios::sync_with_stdio(false);std::cin.tie(0);
#define vec vector
const int N = 5e5+9;
const int INF = 0x7FFFFFFF; //2147483647
const int LOG = log2(N)+1;
const int inf1 = 0x3f3f3f3f; //1061109567
const int inf2 = 0x7f7f7f7f; //2139062143 memset赋值用
using namespace std;
int n,Q,root;
int logn;
int anc[N][LOG];
//anc(i,j):i号节点的第2^(j-1)个祖先节点
int depth[N],max_depth=0;
int head[N],idx=0;
bool vis[N];
struct node{
int to,val,next;
};
node e[N<<1];
void add(int u,int v,int val){
e[idx] = {v,val,head[u]};
head[u] = idx++;
}
void bd(){
cin>>n>>Q>>root;
logn = log2(n);
memset(head,-1,sizeof head);
for(int i=1; i<=n-1 ;++i){
int u,v,val;
cin>>u>>v;
add(u,v,0);
add(v,u,0);
}
}
void dfs(int u,int fa){
anc[u][0] = fa;
//2^(0)=1 v的一号祖先就是父亲
for(int i=head[u] ; i!=-1 ; i=e[i].next){
int v = e[i].to;
if( v==fa ) continue;
//双向边会跑到父亲 故跳过
depth[v] = depth[u]+1;
dfs(v,u);
}
}
void init(){
//预处理dfs后的所有祖先
//pow(2,k) = pow(2,k-1) + pow(2,k-1)
//需要用到j-1 故j从1开始
for(int j = 1 ; j<=logn ;++j){
for(int i=1 ; i<=n ; ++i){
int v = anc[i][j-1];
anc[i][j]= anc[v][j-1];
}
}
}
int LCA(int u,int v){
if(depth[v] > depth[u])
swap(u,v);
//让u是最深的节点
/*
for(int i=logn ; i>=0 ; --i){
if(depth[anc[u][i]] >= depth[v]){
u = anc[u][i];
}
}
*/
for(int i=logn ; i>=0 ; --i ){
if( depth[u]-(1<<i) >= depth[v] ){
u = anc[u][i];
}
}
if(u == v) return u;//提前判定一次
for(int i=logn ; i>=0 ; --i ){
if(anc[u][i] != anc[v][i]){
u= anc[u][i];
v= anc[v][i];
}
}
return anc[u][0];
}
int main(){
ios;
bd();
dfs(root,-1);
init();
for(int i=1 ; i<=Q ; ++i){
int u,v;
cin>>u>>v;
cout<<LCA(u,v)<<"\n";
}
return 0;
}
Tarjan算法
sssxxx
这是树链剖分算法
#include <cmath>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
#define lld long long
#define ios std::ios::sync_with_stdio(false);std::cin.tie(0);
using namespace std;
const int N =5e5+9;
int head[N],idx=0;
bool vis[N];
int n,m,Q,root;
int dep[N],fa[N],top[N],size_[N],son[N];
vector<int>e[N<<1];
void bd(){
cin>>n>>Q>>root;
memset(head,-1,sizeof(head));
for(int i=1 ; i<=n-1 ; ++i){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
}
void dfs1(int u,int fa_){
fa[u] = fa_;
dep[u] = dep[fa_]+1;
size_[u] = 1;
for(int v:e[u]){
if(v==fa_) continue;
dfs1(v,u);
size_[u] = size_[u] + size_[v];
if(size_[son[u]] < size_[v]){
son[u] = v;
}
}
}
void dfs2(int u,int topu){
//topu:u这条链上的顶点
top[u] = topu;
//没有重儿子说明是叶子节点 结束dfs
if(son[u]==0) return;
dfs2(son[u],topu);
//先记录每个点的topu 再更新
for(int v:e[u]){
//上面的dfs搜的就是重儿子son[u] 这里不重复搜了跳过
if(v==fa[u] || v==son[u]) continue;
//剩下的就是轻儿子 轻儿子都是叶子节点 单独成链 这时候端点就是自己
dfs2(v,v);
}
}
int LCA(int u,int v){
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]])
swap(u,v);
u = fa[top[u]];
}
//当他们在同一条重链上时结束循环
//深度小的哪个点就是LCA
return dep[u]<dep[v]?u:v;
}
int main(){
ios;
bd();
dfs1(root,-1);
dfs2(root,root);
while(Q--){
int a,b;
cin>>a>>b;
cout<<LCA(a,b);
cout<<"\n";
}
return 0;
}
【模板】最近公共祖先:LCA算法的更多相关文章
- [模板] 最近公共祖先/lca
简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...
- POJ 1330 Nearest Common Ancestors 【最近公共祖先LCA算法+Tarjan离线算法】
Nearest Common Ancestors Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 20715 Accept ...
- 算法学习笔记(5): 最近公共祖先(LCA)
最近公共祖先(LCA) 目录 最近公共祖先(LCA) 定义 求法 方法一:树上倍增 朴素算法 复杂度分析 方法二:dfs序与ST表 初始化与查询 复杂度分析 方法三:树链剖分 DFS序 性质 重链 重 ...
- LCA(最近公共祖先)算法
参考博客:https://blog.csdn.net/my_sunshine26/article/details/72717112 首先看一下定义,来自于百度百科 LCA(Lowest Common ...
- 【并查集】【树】最近公共祖先LCA-Tarjan算法
最近公共祖先LCA 双链BT 如果每个结点都有一个指针指向它的父结点,于是我们可以从任何一个结点出发,得到一个到达树根结点的单向链表.因此这个问题转换为两个单向链表的第一个公共结点(先分别遍历两个链表 ...
- Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)
Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- 【lhyaaa】最近公共祖先LCA——倍增!!!
高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...
- POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)
POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...
- 【洛谷 p3379】模板-最近公共祖先(图论--倍增算法求LCA)
题目:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 解法:倍增. 1 #include<cstdio> 2 #include<cstdlib> 3 #include ...
随机推荐
- 支付宝 返回的form如何在前端打开
支付宝支付时返回了一段标签标签大概是 <form></form><script></script> 试了innerHtml怎么试都不能用,是那种直接把字 ...
- TRL(Transformer Reinforcement Learning) PPO Trainer 学习笔记
(1) PPO Trainer TRL支持PPO Trainer通过RL训练语言模型上的任何奖励信号.奖励信号可以来自手工制作的规则.指标或使用奖励模型的偏好数据.要获得完整的示例,请查看examp ...
- SSM框架笔记 庆祝学习SSM框架结束!!!
终于在2023/3/29日,黑马程序员旗下的ssm框架视频看完了,也是总结了1万多字的笔记,把黑马的和自己的整合了一下 完结撒花,接下来开始学习SpringBoot和软考中级设计师. 总的来说,我还是 ...
- 高通与At指令:AtCop解析
背景 在某个新基线上移植AT指令,发现有问题,因此收集了这个系列的 文章 作为 这方面的知识补充. 原文作者:laozhuxinlu,本文有删改. 另外,还参考了:https://www.cnblog ...
- 在Linux中使用crontab
背景 虽然不是专业运维,但是在嵌入式开发中还是需要懂一点的.部门内部搞服务器最厉害的就是我了,汗. 参考: https://blog.csdn.net/longgeaisisi/article/det ...
- Xilinx ZYNQ-7000 平台简介
平台介绍 Zynq7000是赛灵思公司(Xilinx)推出的行业第一个可扩展处理平台Zynq系列.旨在为视频监视.汽车驾驶员辅助以及工厂自动化等高端嵌入式应用提供所需的处理与计算性能水平. 在2010 ...
- 【论文阅读】Trajectory-guided Control Prediction for End-to-end Autonomous Driving: A Simple yet Strong Baseline
参考与前言 Summary: leaderboard 现存第一名 TCP,非常simple的设置 取得了很好的效果 论文链接:Trajectory-guided Control Prediction ...
- SAM & 广义 SAM & SA 学习笔记
SAM 定理 SAM 由 parent 树与一张 DAG 构成,他们共用点集. \(endpos(s)\) 表示 \(s\) 出现的所有位置上最后一个字符所处位置的集合. SAM 中 DAG 上每条路 ...
- win10 gvim
前言 现有的vscode太过于繁重,notepad++也不理想,顿来心思,想在win10配置一个gvim环境. 正文 安装gvim,从releases中找一个x64的.exe下载安装即可,安装完毕,把 ...
- PixiJS源码分析系列: 第一章 从最简单的例子入手
从最简单的例子入手分析 PixiJS 源码 我一般是以使用角度作为切入点查看分析源码,例子中用到什么类,什么方法,再入源码. 高屋建瓴的角度咱也做不到啊,毕竟水平有限 pixijs 的源码之前折腾了半 ...