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. mongodb插入文档,更新文档和检索文档

    import com.mongodb.client.*; import com.mongodb.client.MongoClient; import com.mongodb.client.model. ...

  2. 原始指针 [raw pointers]

    指针是一个变量,用于存储对象的内存地址. 指针广泛应用于 C 和 C++: 在堆上分配新对象 通过参数将某些函数传递给其他函数 迭代/遍历数组或其他数据结构的元素 int* p = nullptr; ...

  3. 13-nginx

    关于nginx nginx是提供http服务的中间件. 这里推荐学习nginx的博客:朱双印的博客 安装 nginx的版本 主线版本(Mainline version) #最新版,不稳定 稳定版本(S ...

  4. Java面试知识点(二)super 和 this 关键字

    this this 是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针. this 的用法在 java 中大体可以分为 3 种: 普通的直接引用 这种就不用讲了,this 相当于是指向 ...

  5. P2467 [SDOI2010] 地精部落 学习笔记

    DP 显然我固定第一个是峰,然后再乘以2就是答案,因为一个合法的反转之后也是合法的而且谷峰颠倒了 发现如果设\(dp[i][j]\)表示前\(i\)个山脉,第\(i\)个山脉是高度\(j\)的答案,然 ...

  6. 超大容量 | 瑞芯微RK3588J工业核心板新增16GB DDR + 128GB eMMC配置!

    作为瑞芯微的金牌合作伙伴,创龙科技在2023年9月即推出搭载瑞芯微旗舰级处理器RK3588J的全国产工业核心板--SOM-TL3588. SOM-TL3588工业核心板是基于瑞芯微RK3588J/RK ...

  7. map端join和reduce端join的区别

    MapReduce Join MapJoin和ReduceJoin区别及优化 maptask处理后写到本地,如果再到reduce,又涉及到网络的拷贝. map端join最大优势,可以提前过滤不需要的数 ...

  8. QChart 移动 缩放 加速

    qchart 和 qchartview 的运用的例子 qchart 存在一些问题 一般用在2000个点以下的场景,点多了,就会卡. 解决的办法就是 开启opengl加速. 但这时,对qchartvie ...

  9. 探索Nuxt.js的useFetch:高效数据获取与处理指南

    title: 探索Nuxt.js的useFetch:高效数据获取与处理指南 date: 2024/7/15 updated: 2024/7/15 author: cmdragon excerpt: 摘 ...

  10. stream的优化:java封装的拆箱与装箱的弊端

    authors.stream() .map(author->author.getAge) .map(age->age+10)//Stream<Integer> .filter( ...