LCA最近公共祖先

\[\begin{align}
要求 \ 给出一个树和他的根节点\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)

朴素版本

\[\begin{align*}
& \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;
}

倍增加速算法版本

\[\begin{align}
& 在线版本\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算法的更多相关文章

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

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

  2. POJ 1330 Nearest Common Ancestors 【最近公共祖先LCA算法+Tarjan离线算法】

    Nearest Common Ancestors Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 20715   Accept ...

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

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

  4. LCA(最近公共祖先)算法

    参考博客:https://blog.csdn.net/my_sunshine26/article/details/72717112 首先看一下定义,来自于百度百科 LCA(Lowest Common ...

  5. 【并查集】【树】最近公共祖先LCA-Tarjan算法

    最近公共祖先LCA 双链BT 如果每个结点都有一个指针指向它的父结点,于是我们可以从任何一个结点出发,得到一个到达树根结点的单向链表.因此这个问题转换为两个单向链表的第一个公共结点(先分别遍历两个链表 ...

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

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

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

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

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

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

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

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

  10. 【洛谷 p3379】模板-最近公共祖先(图论--倍增算法求LCA)

    题目:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 解法:倍增. 1 #include<cstdio> 2 #include<cstdlib> 3 #include ...

随机推荐

  1. 哈啰面试:说说Dubbo运行原理?

    Dubbo 是一款高性能.轻量级的开源 RPC(远程过程调用)框架,主要用于构建分布式服务和微服务架构.那 Dubbo 又是如何运行的呢?让我们一起来看. 1.核心组件 要说 Dubbo 运行流程就不 ...

  2. 微信小程序day04基础加强

    一.自定义组件 1.1 组件的创建与引用 首先创建组件 然后我们组件的引用分为局部和全局引用 局部引用就是在当前页面能使用,在当前页面的json文件里面配置 全局引用同样的道理,注意跟page等是同级 ...

  3. 阿里面试:说说@Async实现原理?

    @Async 是 Spring 3.0 提供的一个注解,用于标识某类(下的公共方法)或某方法会执行异步调用. 接下来,我们来看下 @Async 的基本使用和实现原理. 1.基本使用 @Async 基本 ...

  4. Spring的全局(统一)异常处理

    异常处理的三种方式 使用 @ExceptionHandler 注解 实现 HandlerExceptionResolver 接口(SpringMVC) 使用 @RestControllerAdvice ...

  5. 能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?

    这个router有两种模式:hash模式(默认).history模式(需配置mode: 'history') 然后,我们来研究下两者的原理: 我们先来认识下这位朋友#,这个#就是hash符号,中文名哈 ...

  6. Java 集合框架Collection

    集合容器主要用于保存对象,主要分类有三种List.Set.Map List有序.可重复的集合 常见的List有ArrayList.Vector.LinkedList等类 Set无序.不可重复 常见Se ...

  7. Mysql的Innodb和MyISAM引擎的区别

    区别项 Innodb MyISAM  事务  支持  不支持 锁粒度  行锁,适合高并发 表锁,不适合高并发  是否默认  默认  非默认  支持外键  支持外键  不支持  适合场景  读写均衡,写 ...

  8. Spring Cloud提供者actuator依赖

    <!-- actuator依赖 --> <dependency> <groupId>org.springframework.boot</groupId> ...

  9. Oracle 存储过程 捕获异常

    1.带参数插入并带返回值,异常信息 CREATE OR REPLACE PROCEDURE test_pro (v_id in int,v_name in varchar2,app_code out ...

  10. 2024-07-17:用go语言,给定一个整数数组nums, 我们可以重复执行以下操作: 选择数组中的前两个元素并删除它们, 每次操作得到的分数是被删除元素的和。 在保持所有操作的分数相同的前提下,

    2024-07-17:用go语言,给定一个整数数组nums, 我们可以重复执行以下操作: 选择数组中的前两个元素并删除它们, 每次操作得到的分数是被删除元素的和. 在保持所有操作的分数相同的前提下, ...