浅谈LCA(倍增)

Q:为什么我在有些地方看到的是最小公共祖先?

A:最小公共祖先是LCA(Least Common Ancestor)的英文直译,最小公共祖先与最近公共祖先只是叫法不同。

Q:什么是最近公共祖先(LCA)?

A:最近公共祖先的概念是很好理解的。首先,你需要脑补出一棵树(它可以是二叉树,也可以是多叉树。)之后,请你再在你脑补出的树上任取两个点。每个点都可以到达树根,且到达的路径是唯一的,既然两个点都可以到达树根,那么根无疑是这两个点的公共祖先。然而,根却不一定是这两个点的最近公共祖先,相反,离根距离最远且在两条点到根路径上的点才是最近公共祖先(最近公共父节点)。

实现求LCA的方法有很多种,无论是离线还是在线,是TARJAN还是RMQ等等都可以实现。在此,我钟爱的方法:倍增,来实现LCA。

首先,你不可以认为倍增实现LCA和RMQ实现LCA是指的同一回事情,哪怕RMQ的完成是用了倍增思想。事实上,RMQ实现LCA的程序比较繁琐,并且需要你考虑到众多细节。而这些细节的调试在比赛有限的时间内无疑是要爆炸的。比与RMQ相反,倍增实现的代码要比RMQ实现简单一些,并且好脑补,而且容易调试,相信大家一定可以弄明白倍增实现LCA的QWQ

在没有学习倍增写LCA之前,你是怎么样求LCA的呢?至少,我是老老实实地让这两个点一步一步往上移并找出它们的路径第一次交汇的地方。这种方法固然可行、好想,但它的效率实在不高。但是,我们完全可以通过提高“这两个点一步一步往上移”来提高效率。

所以,我们采用倍增的思路来预处理,分别记录这点的祖先,记录为anc[i][j]。即为第i个点往上2^j个祖先。比如说,当j=0时,2^j=1,anc[i][j]是第i个点的上一个节点,即它的父亲节点。

那么该如何预处理出anc数组呢?

int anc[1005][25];
int fa[1005];
vector <int > tree[1005];
int deep[1005];

void dfs(int x)
{
    anc[x][0]=fa[x];
    for (int i=1;i<=22;i++)
    {
        anc[x][i]=anc[anc[x][i-1]][i-1];//倍增思想的体现。不妨在纸上试着画一棵树,脑补一下QWQ
    }
    
    for (int i=0;i<tree[x].size();i++)
    {
        if (tree[x][i]!=fa[x])
        {
            int y=tree[x][i];
            fa[y]=x;//记录父亲节点
            deep[y]=deep[x]+1;//记录深度
            dfs(y);
        }
    }
}

通过从根节点开始的DFS,我们就预处理好了ANC数组。

下面,我们来考虑如何处理LCA查询。即每次给你两点X和Y,求出它们的LCA(X,Y)。在有了ANC数组之后,求出最近公共祖先就会变得很简单。

首先,让X,Y在同一深度上。在大多数情况下,查询给你的两个点X和Y它们的深度是不同的。但是,如果两点的深度相同,我们就可以实现两个点同时倍增比较何时祖先相同。所以,第一步是使X,Y中深度较深的点往上移动直到与另一个点深度相同。当然,点的移动也可以用倍增完成。

然后,当两点深度相同后,同时向上倍增两个点,当它们祖先刚好相同时,这个祖先就是它们的LCA。

如果你还是有一些不理解的话,不妨看LCA实现的代码QAQ

int lca(int x,int y)
{
    if (deep[x]<deep[y]) _swap(x,y);//我们希望X是较深的点。

    for (int i=22;i>=0;i--)//这个循环在完成第一步。
    {
        if (deep[y]<=deep[anc[x][i]]) //不可以丢掉“=“哦Q^Q
        {
            x=anc[x][i];
        }
    }
    
    if (x==y) return x;//如果Y是X的祖先,就可以直接返回结果了。
    
    for (int i=22;i>=0;i--)
    {
        if (anc[x][i]!=anc[y][i]) //第二步。
        {
            x=anc[x][i];
            y=anc[y][i];
        }
    }
    
    return anc[x][0];//注意第二步IF语句的条件。
}

vector 

#include<bits/stdc++.h>
#include <string.h>
#include <algorithm>
#include <algorithm>
#define ms(a,i) memset(a,i,sizeof(a))
using namespace std;
int anc[500005][25];
int fa[500005];
vector <int> tree[500005];
int deep[500005];
int n,m,s;
int maxlog;
void dfs(int x)
{
    anc[x][0]=fa[x];
    for (int i=1;i<=maxlog;i++)
    {
        anc[x][i]=anc[anc[x][i-1]][i-1];//倍增思想的体现。不妨在纸上试着画一棵树,脑补一下QWQ
    }

    for (int i=0;i<tree[x].size();i++)
    {
        if (tree[x][i]!=fa[x])
        {
            int y=tree[x][i];
            fa[y]=x;//记录父亲节点
            deep[y]=deep[x]+1;//记录深度
            dfs(y);
        }
    }
}
int lca(int x,int y)
{
    if (deep[x]<deep[y]) swap(x,y);//我们希望X是较深的点。

    for (int i=maxlog;i>=0;i--)//这个循环在完成第一步。
    {
        if (deep[y]<=deep[anc[x][i]]) //不可以丢掉“=“哦Q^Q
        {
            x=anc[x][i];
        }
    }

    if (x==y) return x;//如果Y是X的祖先,就可以直接返回结果了。

    for (int i=maxlog;i>=0;i--)
    {
        if (anc[x][i]!=anc[y][i]) //第二步。
        {
            x=anc[x][i];
            y=anc[y][i];
        }
    }

    return anc[x][0];//注意第二步IF语句的条件。
}
int main()
{

    scanf("%d%d%d",&n,&m,&s);
    maxlog=log(n)/log(2);
    for(int i=1; i<=n-1; i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        tree[u].push_back(v);
        tree[v].push_back(u);
    }
    fa[s]=s;
    dfs(s);
    anc[s][0]=s;
    for(int i=1; i<=m; i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        int ans=lca(a,b);
        printf("%d\n",ans);//在线回答
    }
    return 0;
}

邻接表

#include<bits/stdc++.h>
#include <string.h>
#include <algorithm>
#include <algorithm>
#define ms(a,i) memset(a,i,sizeof(a))
using namespace std;
int n,m,s;
int anc[500010][20],deep[500010];
struct E
{
    int to,next;
} tree[1000010];
int head[500010],cnt;
void init()
{
    ms(head,-1);
    cnt=0;
}
void add(int u,int v)
{
    tree[cnt].to=v;
    tree[cnt].next=head[u];
    head[u]=cnt++;
}

void dfs(int now,int fa)    //处理出深度和父亲
{
    
            
    for(int i=head[now]; ~i; i=tree[i].next)
    {
        if(tree[i].to!=fa)
        {
            deep[tree[i].to]=deep[now]+1;
            dfs(tree[i].to,now);
            anc[tree[i].to][0]=now;
        }
    }

}

void ready()  //处理出各路祖先
{
    for(int j=1; (1<<j)<=n; j++)
        for(int i=1; i<=n; i++)
            anc[i][j]=anc[anc[i][j-1]][j-1];//原理是爷爷是爸爸的爸爸

}

int lca(int x,int y)
{
    if(deep[x]<deep[y])swap(x,y);//默认x深,否则交换,方便处理
    int maxlog=log(n)/log(2);
    for(int i=maxlog; i>=0; i--)
        if(deep[x]-(1<<i)>=deep[y])
            x=anc[x][i];      //判断看能否直接返回
    if(x==y)return x;

    for(int i=maxlog; i>=0; i--)
        if(anc[x][i]!=anc[y][i])
        {
            x=anc[x][i];
            y=anc[y][i];
        }

    return anc[x][0];//返回父亲
}
int main()
{
    init();
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1; i<=n-1; i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    dfs(s,s);
    ready();
    anc[s][0]=s;
    for(int i=1; i<=m; i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        int ans=lca(a,b);
        printf("%d\n",ans);//在线回答
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41021816/article/details/81736702