学习笔记--最近公共祖先(LCA)的几种求法
前言:
给定一个有根树,若节点\(z\)是两节点\(x,y\)所有公共祖先深度最大的那一个,则称\(z\)是\(x,y\)的最近公共祖先(\(Least Common Ancestors\)),简称\(LCA\).它在许多与树相关问题中发挥较大作用
怎么求
以这题为例:luogu P3379 【模板】最近公共祖先(LCA)
朴素暴力
让深度更大的节点\(x\)向上走至与另一节点\(y\)在同一深度上,然后同时向上走直至相遇.
时间复杂度 \(O(N)\)
代码略
倍增优化
按照上面的思路,但是不是一个一个走,而是先搜一遍树,预处理出\(x\)的第\(2^k (1<=2^k<=max(dep))\)个父亲,存起来.询问时,还是让深度更大的节点\(x\)向上倍增至与另一节点\(y\)在同一深度上,然后一起倍增向上跳
时间复杂度 \(O(M \log N)\)
代码见后
离线Tarjan
这个方法也通俗易懂:我们先将所有询问存起来,DFS一遍树同时我们把节点分为3类
已经回溯完的标记为\(''1''\)
正在dfs的及dfs过但未回溯的标记为\(''2''\)
然后在正在dfs的节点中处理与它有关的询问,若正在回溯已经DFS过的节点\(x\),有个询问是求\(LCA(x,y)\)。
若\(y\)的标记是\(''1''\),显然\(y\)第一个标记为\(''2''\)的祖先就为\(LCA(x,y)\)。万一标记不是\(''1''\)呢?比如当\(y\)是\(x\)祖宗还是没关系,在回溯到\(y\)时,\(y\)就是符合要求的答案
那怎么快速求第一个标记为\(''2''\)的祖先呢?用并查集维护一下就好了.
时间复杂度\(O(N+M)\),较快然而只能离线
代码见后;
树链剖分
如果你不知道树剖的话可以去做做树剖模板或看这位大佬博客https://www.cnblogs.com/George1994/p/7821357.html
代码见后,给大家做个参考
时间复杂度\(O(M \log N)\)
倍增代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <queue>
#include <map>
#define ll long long
#define ri register int
using namespace std;
const int maxn=500005;
const int inf=0x7fffffff;
template <class T>inline void read(T &x){
x=0;int ne=0;char c;
while(!isdigit(c=getchar()))ne=c=='-';
x=c-48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
x=ne?-x:x;
return ;
}
struct Edge{
int ne,to;
}edge[maxn<<1];
int h[maxn],num_edge=0,t;
inline void add_edge(int f,int to){
edge[++num_edge].ne=h[f];
edge[num_edge].to=to;
h[f]=num_edge;
return ;
}
int f[maxn][35],d[maxn];
int n,m,s;
void bfs(){
int u,v;
memset(d,0,sizeof(d));
queue <int>q;
q.push(s);d[s]=1;
//f[s][0]=s;
while(q.size()){
u=q.front();q.pop();
for(ri i=h[u];i;i=edge[i].ne){
v=edge[i].to;
if(d[v])continue;
f[v][0]=u,d[v]=d[u]+1;
//cout<<u<<' '<<v<<endl;
for(ri j=1;j<=t;j++)
{f[v][j]=f[f[v][j-1]][j-1];}//cout<<'8'<<f[v][j]<<endl;};
q.push(v);
}
}
return ;
}
inline int lca(int x,int y){
if(d[x]<d[y])swap(x,y);
if(x==y)return x;
for(ri i=t;i>=0;i--){
if(d[f[x][i]]>=d[y])x=f[x][i];
}
//cout<<'*'<<x<<' '<<y<<endl;
if(x==y)return x;
for(ri i=t;i>=0;i--){
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
}
return f[x][0];
}
int main(){
int x,y,z;
read(n),read(m),read(s);
memset(f,0,sizeof(f));
t=(int)(log(n)/log(2))+1;
for(ri i=1;i<n;i++){
read(x),read(y);
add_edge(x,y);
add_edge(y,x);
//cout<<x<<' '<<y<<endl;
}
bfs();
for(ri i=1;i<=m;i++){
read(x),read(y);
printf("%d\n",lca(x,y));
}
return 0;
}
- Tarjan代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <queue>
#include <map>
#define ll long long
#define ri register int
#define ull unsigned long long
using namespace std;
const int maxn=500005;
const int inf=0x7fffffff;
template <class T>inline void read(T &x){
x=0;int ne=0;char c;
while(!isdigit(c=getchar()))ne=c=='-';
x=c-48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
x=ne?-x:x;
return ;
}
int n,m,s,t;
struct Edge{
int ne,to;
}edge[maxn<<1];
struct QU{
int d,id;
QU(int x,int y){d=x,id=y;}
QU(){;}
};
vector <QU>q[maxn];
int h[maxn],num_edge=0,ans[maxn];
inline void add_edge(int f,int to){
edge[++num_edge].ne=h[f];
edge[num_edge].to=to;
h[f]=num_edge;
return;
}
int fa[maxn],vis[maxn];
int get(int x){
if(fa[x]!=x)fa[x]=get(fa[x]);
return fa[x];
}
void dfs(int cur){
int u,v;
vis[cur]=1;
for(ri i=h[cur];i;i=edge[i].ne){
v=edge[i].to;
if(vis[v])continue;
dfs(v);
fa[v]=cur;//dfs后再合并
}
for(ri i=0;i<q[cur].size();i++){
u=q[cur][i].d,v=q[cur][i].id;
if(vis[u]==2){
ans[v]=get(u);
}
}
vis[cur]=2;//dfs过
return ;
}
int main(){
int x,y;
read(n),read(m),read(s);
for(ri i=1;i<n;i++){
read(x),read(y);
add_edge(x,y);
add_edge(y,x);
fa[i]=i;
}fa[n]=n;
for(ri i=1;i<=m;i++){
read(x),read(y);
//q[x].push_back(y);q[y].push_back(x);
q[x].push_back(QU(y,i));
q[y].push_back(QU(x,i));
}
dfs(s);
for(ri i=1;i<=m;i++){
printf("%d\n",ans[i]);
}
return 0;
}
- 树链剖分代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <map>
#include <queue>
#define ll long long
#define ri register int
using namespace std;
const int maxn=500005;
const int inf=0x7fffffff;
template <class T>inline void read(T &x){
x=0;int ne=0;char c;
while(!isdigit(c=getchar()))ne=c=='-';
x=c-48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
x=ne?-x:x;
}
int n,m,s;
struct Edge{
int ne,to;
}edge[maxn<<1];
int h[maxn],num_edge=0;
inline void add_edge(int f,int to){
edge[++num_edge].ne=h[f];
edge[num_edge].to=to;
h[f]=num_edge;
}
int dep[maxn],fa[maxn],size[maxn],top[maxn],son[maxn];//son--重儿子 top--重链顶端或轻链节点
void dfs_1(int u){
int v;
size[u]=1;
for(ri i=h[u];i;i=edge[i].ne){
v=edge[i].to;
if(dep[v])continue;
dep[v]=dep[u]+1,fa[v]=u;
dfs_1(v);
size[u]+=size[v];
if(!son[u]||size[son[u]]<size[v])son[u]=v;
}
return;
}
void dfs_2(int u,int t){//t--top重链起点
int v;
top[u]=t;
if(!son[u])return ;//叶子节点
dfs_2(son[u],t); //dfs重链上各节点
for(ri i=h[u];i;i=edge[i].ne){
v=edge[i].to;
if(v==fa[u])continue;
if(v!=son[u])dfs_2(v,v);//dfs下一条链的起点
}
return ;
}
int main(){
int x,y;
read(n),read(m),read(s);
for(ri i=1;i<n;i++){
read(x),read(y);
add_edge(x,y);
add_edge(y,x);
}
dep[s]=1;
dfs_1(s);
dfs_2(s,s);
for(ri i=1;i<=m;i++){
read(x),read(y);
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])y=fa[top[y]];
else x=fa[top[x]];
}//此时在一条链上
if(dep[x]>dep[y])swap(x,y);
printf("%d\n",x);
}
return 0;
}
学习笔记--最近公共祖先(LCA)的几种求法的更多相关文章
- [一本通学习笔记] 最近公共祖先LCA
本节内容过于暴力没什么好说的.借着这个专题改掉写倍增的陋习,虽然写链剖代码长了点不过常数小还是很香. 10130. 「一本通 4.4 例 1」点的距离 #include <bits/stdc++ ...
- 最近公共祖先(LCA)的三种求解方法
转载来自:https://blog.andrewei.info/2015/10/08/e6-9c-80-e8-bf-91-e5-85-ac-e5-85-b1-e7-a5-96-e5-85-88lca- ...
- Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)
Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...
- POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)
POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- [模板] 最近公共祖先/lca
简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...
- 最近公共祖先 LCA 递归非递归
给定一棵二叉树,找到两个节点的最近公共父节点(LCA).最近公共祖先是两个节点的公共的祖先节点且具有最大深度.假设给出的两个节点都在树中存在. dfs递归写法 查找两个node的最近公共祖先,分三种情 ...
- 【lhyaaa】最近公共祖先LCA——倍增!!!
高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...
- 最近公共祖先 LCA Tarjan算法
来自:http://www.cnblogs.com/ylfdrib/archive/2010/11/03/1867901.html 对于一棵有根树,就会有父亲结点,祖先结点,当然最近公共祖先就是这两个 ...
随机推荐
- (四)C语言之函数
- IP输出 之 分片ip_fragment、ip_do_fragment
概述 ip_fragment函数用于判断是否进行分片,在没有设置DF标记的情况下进入分片,如果设置了DF标记,则继续判断,如果不允许DF分片或者收到的最大分片大于MTU大小,则回复ICMP,释放skb ...
- beta week 2/2 Scrum立会报告+燃尽图 07
此作业要求参见https://edu.cnblogs.com/campus/nenu/2019fall/homework/9959 一.小组情况 组长:贺敬文组员:彭思雨 王志文 位军营 徐丽君队名: ...
- Linux 如何查看端口与进程占用情况
1 lsof -i:port 查看端口使用情况 lsof -i 如果出现 command not found,直接yum install lsof即可. (1) lsof -i lsof -i 用以 ...
- C++ 学习时的错误记录
1. 关于C++相关的文件扩展名 c++程序中的头文件扩展名包括: .h .hpp .hxx C++程序中源文件的扩展名包括: .cc .cpp .cxx 2.C++程序编译过程 3. 处理错误 4. ...
- Ngrinder脚本开发各细节锦集(groovy)
Ngrinder脚本开发各细节锦集(groovy) 1.生成随机字符串(import org.apache.commons.lang.RandomStringUtils) 数字:RandomStrin ...
- koa 项目实战(一)创建项目
1.安装模块 npm install koa koa-router --save npm install -g nodemon 2.入口文件 app.js const Koa = require('k ...
- Spring AOP增强(Advice)
Sring AOP通过PointCut来指定在那些类的那些方法上织入横切逻辑,通过Advice来指定在切点上具体做什么事情.如方法前做什么,方法后做什么,抛出异常做什么. Spring中有两种方式定义 ...
- YAML基础知识及搭建一台简洁版guestbook
一,前言 前面我们已经搭建过简易版k8s集群了,在此基础上可以搭建一个简洁版guestbook ,以便来学习k8s创建pod的整个过程. 二,在此之前,我们还需要学习一下YAML基础知识 YAML 基 ...
- RN 图片处理 resizeMode
Image组件必须在样式中声明图片的宽和高.如果没有声明,则图片将不会被呈现在界面上. 我们一般将Image定义的宽和高乘以当前运行环境的像素密度称为Image的实际宽高. 当Image的实际宽 ...