学习笔记--最近公共祖先(LCA)的几种求法

  • 前言:

    给定一个有根树,若节点\(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;
}

猜你喜欢

转载自www.cnblogs.com/Rye-Catcher/p/9258335.html