关于求LCA三种方法

这里介绍LCA的三种方法(倍增,RMQ,tarjan) ps:以lg3379为例.

1.倍增

可以理解为暴力的优化.先让两个点跳在同一高度,再一起跳2^k次倍祖先.

难点:倍增数组转移方程:bz[i][j]=bz[bz[i][j-1]][j-1]

#include<iostream>
#include<cstdio>
using namespace std;
#define R register
#define e exit(0)
const int maxn=5e6+10;
int n,m,k,s,cnt,head[maxn],fa[maxn],h[maxn],bz[maxn][30];
struct shu{
    int to,next;
}tree[maxn*2];
inline int fd()
{
    int s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            s=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        t=t*10+c-'0';
        c=getchar();
    }
    return s*t;
}
void add(int from,int to)
{
    tree[++cnt].to=to;
    tree[cnt].next=head[from];
    head[from]=cnt;
}
void dfs(int x)
{   
    for(R int k=head[x];k;k=tree[k].next)
    {
        int to=tree[k].to;
        if(!h[to])
        {
            fa[to]=x;
            h[to]=h[x]+1;
            dfs(to);
        }
    }
}
int main()
{
    freopen("s.in","r",stdin);
    freopen("s.out","w",stdout);
    n=fd(),m=fd(),s=fd();
    for(R int i=1;i<=n-1;++i)
    {
        int from=fd(),to=fd();
        add(from,to),add(to,from);
    }
    h[s]=1,fa[s]=s;
    dfs(s);
    for(R int i=1;i<=n;++i)
        bz[i][0]=fa[i];//为fa[i],不为i,因为2^0==1;
    for(R int j=1;j<=19;++j)
        for(R int i=1;i<=n;++i)
            bz[i][j]=bz[bz[i][j-1]][j-1];
    for(R int i=1;i<=m;++i)
    {
        int x=fd(),y=fd();
        if(h[x]<h[y])
            swap(x,y);
        for(R int k=19;k>=0;--k)
            if(h[bz[x][k]]>=h[y])
                x=bz[x][k];
        if(x==y)
        {
            printf("%d\n",x);
            continue;
        }
        for(R int k=19;k>=0;--k)
            if(bz[x][k]!=bz[y][k])
                x=bz[x][k],y=bz[y][k];
            //如果bz[x][k]==bz[y][k],说明这个点是祖先但不一定是LCA,我们贪心地认为这不是LCA以求最优.
                //其次这不是一级一级跳,是在原来以跳的2^n次方上再跳2^m次方.
        printf("%d\n",fa[x]);//bz[x][k]==bz[y][k]就退出,bz[x][k]未赋值给x,bz[y][k]未赋值给y,所以输出bz的又上一级,即fa[x]或fa[y];
    }
    return 0;
}

 

2.RMQ

提前说明这种算法比较慢,且很容易错.写在这里便于对RMQ的理解复习.

大概思路是建树后跑一遍dfs以求dfn,用数组记录点在dfn中的第一次出现的位置.

LCA一定在这两个点的dfn区间内,用RMQ求区间深度最小即LCA.有一点说

明的是在构建RMQ时用数组用同样的方式记录最小值对应的点.

#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define R register
const int maxn=5*1e6+10;
int n,m,s,cnt,tot,logn[maxn],head[maxn],dfn[maxn],h[maxn],fir[maxn],f[maxn][30],rec[maxn][30];
struct bian{
    int to,next;
}len[maxn*2];
inline int fd()
{
    int s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            s=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        t=t*10+c-'0';
        c=getchar();
    }
    return s*t;
}
void add(int from,int to)
{
    len[++cnt].to=to;
    len[cnt].next=head[from];
    head[from]=cnt;
}
void dfs(int x,int dep)
{
    fir[x]=++tot,dfn[tot]=x,h[tot]=dep;
    for(R int k=head[x];k;k=len[k].next)
    {
        int to=len[k].to;
        if(!fir[to])
        {
            dfs(to,dep+1);
            dfn[++tot]=x,h[tot]=dep;//回溯也要记录.
        }
    }
}
int main()
{
    freopen("s.in","r",stdin);
    freopen("s.out","w",stdout);
    n=fd(),m=fd(),s=fd();
    for(R int i=1;i<n;++i)
    {
        int from=fd(),to=fd();
        add(from,to),add(to,from);
    }
    dfs(s,1);
    logn[0]=-1;
    for(R int i=1;i<=tot;++i)
        f[i][0]=h[i],rec[i][0]=dfn[i],logn[i]=logn[i>>1]+1;
    for(R int j=1;j<=20;++j)
        for(R int i=1;i+(1<<j)-1<=tot;++i)
        {
            if(f[i][j-1]<f[i+(1<<j-1)][j-1])
            {
                f[i][j]=f[i][j-1];
                rec[i][j]=rec[i][j-1];
            }
            else{
                f[i][j]=f[i+(1<<j-1)][j-1];
                rec[i][j]=rec[i+(1<<j-1)][j-1];//跟着变化记录点.
            }
        }
    for(R int i=1;i<=m;++i)
    {
        int x=fd(),y=fd();
        int l=fir[x],r=fir[y];
        if(l>r)
            swap(l,r);
        int num=logn[r-l+1];
        if(f[l][num]<f[r-(1<<num)+1][num])
            printf("%d\n",rec[l][num]);
        else printf("%d\n",rec[r-(1<<num)+1][num]);
    }
    return 0;
}

 

3.tarjan

提前说明为离线算法,复杂度O(n+q),较快.ps:q为询问次数.

大概思路为构建已经存在的树与询问树.先dfs遍历已经存在的树,

再从遍历的节点中寻找询问树中与其关联的节点.看询问树中的节点是否

遍历过,遍历过就一定走过了LCA,用并查集维护最开始的爸爸则是LCA.

其中疑问,最开始的爸爸不是根结点吗?不,根节点的儿子还未遍历回去,还未认这个爸爸.

以此类推,存在树中的所有子树结构均满足这中关系.

#include<iostream>
#include<cstdio>
using namespace std;
#define R register
const int maxn=1e6+10;
int n,m,s,cnt,qcnt,fa[maxn],head[maxn],qhead[maxn],vis[maxn],lca[maxn*2];
struct bian{
    int to,next;
}len[maxn*2];
struct qbian{
    int to,next;
}qlen[maxn*2];
inline int fd()
{
    int s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9')
    {   
        if(c=='-')
            s=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        t=t*10+c-'0';
        c=getchar();    
    }
    return s*t;
}
void add(int from,int to)
{
    len[++cnt].to=to;
    len[cnt].next=head[from];
    head[from]=cnt;
}
void qadd(int from,int to)
{
    qlen[++qcnt].to=to;
    qlen[qcnt].next=qhead[from];
    qhead[from]=qcnt;
}
int find(int x)
{
    if(x==fa[x])
        return x;
    else return fa[x]=find(fa[x]);
}
void tarjan(int x)
{
    vis[x]=1;
    for(R int k=head[x];k;k=len[k].next)
    {
        int to=len[k].to;
        if(vis[to])
            continue;
        tarjan(to);
        fa[to]=x;//未回溯,未认father.
    }
    for(R int k=qhead[x];k;k=qlen[k].next)
    {
        int to=qlen[k].to;
        if(vis[to])
        {
            lca[k]=find(to);
            if(k%2)
                lca[k+1]=lca[k];
            else
                lca[k-1]=lca[k];
            //两个为一组. 
        }
    }
}
int main()
{
    freopen("s.in","r",stdin);
    freopen("s.out","w",stdout);
    n=fd(),m=fd(),s=fd();
    for(R int i=1;i<=n;++i) 
        fa[i]=i;
    for(R int i=1;i<n;++i)
    {
        int from=fd(),to=fd();
        add(from,to),add(to,from);
    }   
    for(R int i=1;i<=m;++i)
    {
        int from=fd(),to=fd();
        qadd(from,to),qadd(to,from);
    }
    tarjan(s);  
    for(R int i=1;i<=m;++i)
        printf("%d\n",lca[i*2]);
    return 0;
}

 

posted @ 2019-07-16 14:51  xqyxqy  阅读(712)  评论(0编辑  收藏  举报