• 前言:

    给定一个有根树,若节点\(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. (十)C语言之putchar、getchar

  2. SpringSecurity开发

    RBAC 数据库权限表结构设计与创建 sys_user表 CREATE TABLE sysuser (id INT(10) unsigned PRIMARY KEY NOT NULL COMMENT ...

  3. Nodepad++中将Tab键替换为空格

    Nodepad++是一个非常优秀的文本编辑工具,本人经常使用其编辑shell脚本,如果不进行设置,Tab键和空格混用,脚本上传到linux后,格式错乱,不容易查看. 设置方式 菜单栏选择"设 ...

  4. setHasFixedSize(true)的意义 (转)

    RecyclerView setHasFixedSize(true)的意义 2017年07月07日 16:23:04 阅读数:6831 <span style="font-size:1 ...

  5. js evenloop

    一.宏任务 vs 微任务 1.macrotask setTimeOut . setInterval . setImmediate . I/O . 各种callback.UI渲染等 优先级: 主代码块 ...

  6. LC 465. Optimal Account Balancing 【lock,hard】

    A group of friends went on holiday and sometimes lent each other money. For example, Alice paid for ...

  7. 设置placeholder 颜色

    ::-webkit-input-placeholder { /* WebKit browsers */ color: rgb(100, 193, 173); } :-moz-placeholder { ...

  8. Spring-AOP简单实现方式

    AOP的专业术语: 1.创建Maven管理项目: pom.xml导入依赖 <properties> <!-- springframe 版本控制 --> <spring.v ...

  9. Session中的方法概述

    一.操作实体对象 delete()把持久化或游离转为删除状态 save()把临时状态变为持久化状态(交给Sessioin管理) saveOrUpdate()把临时或游离状态转为持久化状态 update ...

  10. redis 之django-redis

    redis之django-redis   自定义连接池 这种方式跟普通py文件操作redis一样,代码如下: views.py import redis from django.shortcuts i ...