【模板】最近公共祖先: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 ...
随机推荐
- Excel poi 设置单元格格式 发现不可读内容 已修复的记录: /xl/worksheets/sheet1.xml 部分的问题(巨坑)
Excel poi 设置单元格格式 发现不可读内容 已修复的记录: /xl/worksheets/sheet1.xml 部分的问题(巨坑) 1.先设置值,后设置样式. 正确的是:先设置样式,后设置值. ...
- 以沙箱的方式运行容器:安全容器gvisor
目录 一.系统环境 二.前言 三.安全容器隔离技术简介 四.Gvisor简介 五.容器runtime简介 六.docker容器缺陷 七.配置docker使用gVisor作为runtime 7.1 安装 ...
- Java跳动爱心代码
1.计算爱心曲线上的点的公式 计算爱心曲线上的点的公式通常基于参数方程.以下是两种常见的参数方程表示方法,用于绘制爱心曲线: 1.1基于 (x, y) 坐标的参数方程 x = a * (2 * cos ...
- mysql多表删除指定记录
在Mysql4.0之后,mysql开始支持跨表delete. Mysql可以在一个sql语句中同时删除多表记录,也可以根据多个表之间的关系来删除某一个表中的记录. 假定我们有两张表:Product表和 ...
- 深入理解Prometheus: Kubernetes环境中的监控实践
在这篇文章中,我们深入探讨了Prometheus在Kubernetes环境中的应用,涵盖了从基础概念到实战应用的全面介绍.内容包括Prometheus的架构.数据模型.PromQL查询语言,以及在Ku ...
- 算法金 | 致敬深度学习三巨头:不愧是腾讯,LeNet问的巨细。。。
大侠幸会,在下全网同名「算法金」 0 基础转 AI 上岸,多个算法赛 Top 「日更万日,让更多人享受智能乐趣」 抱个拳,送个礼 读者参加面试,竟然在 LeNet 这个基础算法上被吊打~ LeNe ...
- SpringBoot学习备忘
在 mapper.xml 中的 like 的写法 db1.name like "%"#{name}"%" 参考mybatis mapper.xml中like的写 ...
- Docker部署JavaWeb项目(Tomcat环境)
一.环境准备 1.首先准备一台Centos 7的虚拟机并安装Docker. 2.准备好Tomcat.JDK的安装包以及该java项目的war包. 二.编写Dockerfile Dockerfile是一 ...
- 通过vscode写博客
通过Vscode写博客到博客园 前言 在以前的写作方式都是通过博客园内置的markdown进行工作,但是在实际使用过程中,感觉不是很方便,所以找到了用VSCode插件写作的方法. 所需插件 博客园Cn ...
- position的值, relative和absolute分别是相对于谁进行定位的?
relative: 相对定位,相对于自己本身在正常文档流中的位置进行定位 相对它原来的位置,在走100px.原来在标准流中的位置继续占有. absolute: 生成绝对定位,相对于最近一级定位不为s ...