• 前言:

    给定一个有根树,若节点\(z\)是两节点\(x,y\)所有公共祖先深度最大的那一个,则称\(z\)是\(x,y\)的最近公共祖先(\(Least Common Ancestors\)),简称\(LCA\).它在许多与树相关问题中发挥较大作用

  • 怎么求

    以这题为例:luogu P3379 【模板】最近公共祖先(LCA)

    1. 朴素暴力

      让深度更大的节点\(x\)向上走至与另一节点\(y\)在同一深度上,然后同时向上走直至相遇.

      时间复杂度 \(O(N)\)

      代码略

    2. 倍增优化

      按照上面的思路,但是不是一个一个走,而是先搜一遍树,预处理出\(x\)的第\(2^k (1<=2^k<=max(dep))\)个父亲,存起来.询问时,还是让深度更大的节点\(x\)向上倍增至与另一节点\(y\)在同一深度上,然后一起倍增向上跳

      时间复杂度 \(O(M \log N)\)

      代码见后

    3. 离线Tarjan

      这个方法也通俗易懂:我们先将所有询问存起来,DFS一遍树同时我们把节点分为3类

      1. 已经回溯完的标记为\(''1''\)

      2. 正在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)\),较快然而只能离线

      代码见后;

    4. 树链剖分

      如果你不知道树剖的话可以去做做树剖模板或看这位大佬博客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)的几种求法的更多相关文章

  1. [一本通学习笔记] 最近公共祖先LCA

    本节内容过于暴力没什么好说的.借着这个专题改掉写倍增的陋习,虽然写链剖代码长了点不过常数小还是很香. 10130. 「一本通 4.4 例 1」点的距离 #include <bits/stdc++ ...

  2. 最近公共祖先(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- ...

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

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

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

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

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

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

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

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

  7. 最近公共祖先 LCA 递归非递归

    给定一棵二叉树,找到两个节点的最近公共父节点(LCA).最近公共祖先是两个节点的公共的祖先节点且具有最大深度.假设给出的两个节点都在树中存在. dfs递归写法 查找两个node的最近公共祖先,分三种情 ...

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

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

  9. 最近公共祖先 LCA Tarjan算法

    来自:http://www.cnblogs.com/ylfdrib/archive/2010/11/03/1867901.html 对于一棵有根树,就会有父亲结点,祖先结点,当然最近公共祖先就是这两个 ...

随机推荐

  1. TCP输入 之 tcp_rcv_established

    概述 tcp_rcv_established用于处理已连接状态下的输入,处理过程根据首部预测字段分为快速路径和慢速路径: 1. 在快路中,对是有有数据负荷进行不同处理: (1) 若无数据,则处理输入a ...

  2. Java-JVM 运行时内存结构(Run-Time Data Areas)

    Java 虚拟机定义了在程序执行期间使用的各种运行时数据区域. 其中一些数据区域所有线程共享,在 Java 虚拟机(JVM)启动时创建,仅在 Java 虚拟机退出时销毁. 还有一些数据区域是每个线程的 ...

  3. P4138 [JOISC2014]挂饰

    P4138 [JOISC2014]挂饰 ◦          N个装在手机上的挂饰.挂饰附有可以挂其他挂件的挂钩.每个挂件要么直接挂在手机上,要么挂在其他挂件的挂钩上.直接挂在手机上的挂件最多有1个. ...

  4. 最少步数&P1443 马的遍历

      1330:[例8.3]最少步数 s数组:记录(1,1)到达每一点需要的最少步数 s[1][1]自然为 0,其余初始化为 -1 que数组:que[#][1] 表示(1,1)可到达点的 x 坐标 q ...

  5. WebStrom编程小技巧--HTML快速创建指定id或者类名的div

    打印div标签快速方法:“先打出#yz,然后Tab键补全即可获得<div id="yz"></div>同理:我们也可以先打出“.tz"然后Tab键 ...

  6. 非监督的降维算法--PCA

    PCA是一种非监督学习算法,它能够在保留大多数有用信息的情况下,有效降低数据纬度. 它主要应用在以下三个方面: 1. 提升算法速度 2. 压缩数据,减小内存.硬盘空间的消耗 3. 图示化数据,将高纬数 ...

  7. Python安装远程调试Android需要的扩展脚本

    http://android-scripting.googlecode.com/hg/python/ase/android.py 拷贝到/Python27/Lib/site-packages这个目录下 ...

  8. yum安装epel源

    国内yum源的安装(163,阿里云,epel)   国内yum源的安装(163,阿里云,epel) ----阿里云镜像源 1.备份 mv /etc/yum.repos.d/CentOS-Base.re ...

  9. 行为验证码的asp.net MVC实现方式 qq521877626

    界面http://localhost:你的服务器/Code/index 实现步骤: 注册账号https://www.geetest.com   新增验证 下载demo (url:http://docs ...

  10. WPF 非UI线程更新UI界面的各种方法小结

    转载:https://www.cnblogs.com/bdbw2012/articles/3777594.html 我们知道只有UI线程才能更新UI界面,其他线程访问UI控件被认为是非法的.但是我们在 ...