LCA的几种常见求法

LCA算是OI中比较有用的算法了,下面来介绍LCA的三种大众求法。

暴力寻找法

这种是将一个点到根节点上所有的点都标记上,然后,有另一个点想根节点找,第一个有标记的便是LCA。这种的时间复杂度为O(n^2),算是比较慢的了。
上代码

#include<bits/stdc++.h>
using namespace std;
int n,m,s,ver[1000001],hed[1000001],nxt[1000001],tot=0,deep[500001],fa[500001],bj[500001],b[500001];
void add(int x,int y){
    ver[++tot]=y,nxt[tot]=hed[x],hed[x]=tot;
}
void dfs(int x){
    for(int i=hed[x];i;i=nxt[i]){
        if(!b[ver[i]]){
            b[ver[i]]=1;
            fa[ver[i]]=x;
            dfs(ver[i]);
        }   
    }
}
int main(){
    scanf("%lld%lld%lld",&n,&m,&s);
    for(int i=1,x,y;i<n;i++){
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    b[s]=1;
    dfs(s);
    for(int i=1,x,y;i<=m;i++){
        memset(bj,0,sizeof(bj));
        memset(b,0,sizeof(b));
        scanf("%d%d",&x,&y);
        bj[x]=1;
        for(int i=fa[x];i;i=fa[i])
            bj[i]=1;
        if(bj[y]){
            printf("%d\n",y);
            continue;
        }
        for(int i=fa[y];i;i=fa[i])
            if(bj[i]){
                printf("%d\n",i);
                break;
            }   
    }
    return 0;
}
二进制拆分法

这种是第一种的优化版,记录每个点的深度,若深度不同,根据二进制思想将其调至深度相同,然后二进制向上找,直至两点祖先一样,这点即为LCA。时间复杂度O(nlogn).
上代码

#include<bits/stdc++.h>
using namespace std;
int t,n,m,s,ver[1000001],hed[500001],nxt[1000001],tot=0,deep[500001],f[500001][20];
queue<int>q;
void add(int x,int y){
    ver[++tot]=y,nxt[tot]=hed[x],hed[x]=tot;
}
void bfs(int a){
    q.push(a),deep[a]=1;
    while(q.size()){
        int x=q.front();
        q.pop();
        for(int i=hed[x],y;i;i=nxt[i]){
            if(deep[y=ver[i]]) continue;
            deep[y]=deep[x]+1;
            f[y][0]=x;
            for(int j=1;j<=t;j++)
                f[y][j]=f[f[y][j-1]][j-1];
            q.push(y);
        }
    }
}
int lca(int x,int y){
    if(deep[x]>deep[y])
        swap(x,y);
    for(int i=t;i>=0;i--)
        if(deep[f[y][i]]>=deep[x])
            y=f[y][i];
    if(x==y) return x;
    for(int 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(){
    scanf("%lld%lld%lld",&n,&m,&s);
    for(int i=1,x,y;i<n;i++){
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    t=(int)(log(n)/log(2))+1;
    bfs(s);
    for(int i=1,x,y;i<=m;i++){
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
}
LCA的Tarjan算法

这个算法的本质是使用并查集对第一种的优化,是一个离线算法。
总思路就是每进入一个节点u的深搜,就把整个树的一部分看作以节点u为根节点的小树,再搜索其他的节点。每搜索完一个点后,如果该点和另一个已搜索完点为需要查询LCA的点,则这两点的LCA为另一个点的现在的祖先。
1.先建立两个链表,一个为树的各条边,另一个是需要查询最近公共祖先的两节点。
2.建好后,从根节点开始进行一遍深搜。
3.先把该节点u的father设为他自己(也就是只看大树的一部分,把那一部分看作是一棵树),搜索与此节点相连的所有点v,如果点v没被搜索过,则进入点v的深搜,深搜完后把点v的father设为点u。
4.深搜完一点u后,开始判断节点u与另一节点v是否满足求LCA的条件,满足则将结果存入数组中。
5.搜索完所有点,自动退出初始的第一个深搜,输出结果。
时间复杂度为O(m+n)
上代码

#include<bits/stdc++.h>
using namespace std;
template<typename Type>inline void read(Type &xx){
    Type f=1;char ch;xx=0;
    for(ch=getchar();ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())xx=xx*10+ch-'0';
    xx*=f;
}
struct edge{
    int to,next;
}e[1000001];
struct questions{
    int to,next,same,num;
    bool flag;
    questions(){flag=false;}
}q[1000001];
bool b[500001];
int head[500001],que[500001],father[500001],n,m,s,nume=0,numq=0,ans[500001];
void add_edge(int x,int y){
    e[++nume].to=y,e[nume].next=head[x];
    head[x]=nume,e[++nume].to=x;
    e[nume].next=head[y],head[y]=nume;
}
void add_que(int x,int y,int k){
    q[++numq].to=y,q[numq].same=numq+1;
    q[numq].next=que[x],q[numq].num=k;
    que[x]=numq,q[++numq].to=x;
    q[numq].same=numq-1,q[numq].next=que[y];
    q[numq].num=k,que[y]=numq;
}
int find(int x){
    if(father[x]!=x)father[x]=find(father[x]);
    return father[x];
}
void unionn(int x,int y){
    father[find(y)]=find(x);
}
void LCA(int point,int f){
    for(int i=head[point];i!=0;i=e[i].next)
        if(e[i].to!=f&&!b[e[i].to]){
            LCA(e[i].to,point);
            unionn(point,e[i].to);
            b[e[i].to]=1;
        }
    for(int i=que[point];i!=0;i=q[i].next)
        if(!q[i].flag&&b[q[i].to]){
            ans[q[i].num]=find(q[i].to);
            q[i].flag=1;
            q[q[i].same].flag=1;
        }
}
int main(){
    read(n);read(m);read(s);
    for(int i=1,x,y;i<=n-1;i++){
        father[i]=i;
        read(x);read(y);
        add_edge(x,y);
    }
    father[n]=n;
    for(int i=1,x,y;i<=m;i++){
        read(x);read(y);
        add_que(x,y,i);
    }
    LCA(s,0);
    for(int i=1;i<=m;i++)
        printf("%d\n",ans[i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/sjzezwzy/article/details/80947262