最近公共祖先LCA的求法

Posted dsb-y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最近公共祖先LCA的求法相关的知识,希望对你有一定的参考价值。

一道例题:

 最近公共祖先

Description

  给你一棵有根树,要求你计算出m对结点的最近公共祖先。

Input

  输入文件的第一行包含两个整数n和m(2<=n,m<=200,000),其中n为结点个数,结点编号为1到n;m表示询问次数。
  接下来n-1行,每行两个整数x和y,表示结点x是结点y的父亲结点;
  接下来的m行,每行两个整数a和b,要求计算出结点a和b的最近公共祖先。

Output

  输出文件共m行,分别为每对结点的最近公共祖先编号。

Sample Input

5 3
2 3
3 4
3 1
1 5
3 5
4 5
1 2

Sample Output

3
3
2
 
一.倍增法
最常见也是最实用的方法,可在线询问
时间复杂度O(nlogn)
每个询问的复杂度O(logn)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,cnt=0;
struct node1{int to,next;}a[200005];
int h[200005],prt[200005],f[200005][25],d[200005];
void add(int x,int y){cnt++;a[cnt].to=y;a[cnt].next=h[x];h[x]=cnt;}
void dfs(int x,int deep)
{
    d[x]=deep;
    int i;
    for(i=h[x];i;i=a[i].next)
      dfs(a[i].to,deep+1);
}
void ST()
{    
    
    memset(f,-1,sizeof(f));
    int i,j;
    for(i=1;i<=n;i++)f[i][0]=prt[i];
    for(j=1;(1<<j)<=n;j++)
      for(i=1;i<=n;i++)
        if(f[i][j-1]!=-1)f[i][j]=f[f[i][j-1]][j-1];
}
int LCA(int x,int y)
{
    int i,j,k;
    if(d[x]<d[y])swap(x,y);
    k=int(log(d[x])/log(2));
    for(i=k;i>=0;i--)
      if(d[x]-(1<<i)>=d[y])x=f[x][i];
    if(x==y)return y;
    for(i=k;i>=0;i--)
      if(f[x][i]!=-1&&f[x][i]!=f[y][i]){x=f[x][i];y=f[y][i];}
    return f[x][0];  
}
int main()
{
    int x,y,i,j,root;
    cin>>n>>m;
    for(i=1;i<=n-1;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        prt[y]=x;
    }
    root=1;
    while(prt[root]!=0)root=prt[root];
    dfs(root,1);
    ST();
    for(i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d
",LCA(x,y));
    }
    return 0;
}

二.tarjan求法

此坑待填
三.树链剖分求法
支持在线。
时间复杂度O(nlogn),一般比倍增法快
每个询问的复杂度O(logn)
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
int n,m,d[200005],prt[200005],cnt,son[200005],size[200005],tot,h[200005],top[200005];
struct node{int to,next;}a[200005];
void add(int x,int y){cnt++;a[cnt].to=y;a[cnt].next=h[x];h[x]=cnt;}
void dfs1(int x,int fa,int deep)
{
    int i,j,maxsize=0;
    d[x]=deep;
    size[x]=1;
    for(i=h[x];i;i=a[i].next)
    {
        j=a[i].to;
        dfs1(j,x,deep+1);
        if(size[j]>maxsize)maxsize=size[j],son[x]=j;
        size[x]+=size[j];
    }
}
void dfs2(int x,int anc)
{
    int i,j;
    top[x]=anc;
    if(son[x]!=-1)dfs2(son[x],anc);
    for(i=h[x];i;i=a[i].next)
    {
        j=a[i].to;
        if(j==son[x])continue;
        dfs2(j,j);
    }
}
int ask(int x,int y)
{
    int f1=top[x],f2=top[y];
    while(f1!=f2)
    {
        if(d[f1]<d[f2])swap(f1,f2),swap(x,y);
        x=prt[f1];f1=top[x];
    }
    if(x==y)return x;
    if(d[x]>d[y])swap(x,y);
    return x;
}
int main()
{
    int i,j,k,x,y,root;
    scanf("%d%d",&n,&m);
    for(i=2;i<=n;i++)scanf("%d%d",&x,&y),add(x,y),prt[y]=x;
    for(i=1;i<=n;i++)if(!prt[i]){root=i;break;}
    memset(son,-1,sizeof(son));
    dfs1(root,0,1);
    dfs2(root,root);
    for(i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d
",ask(x,y));
    }
    return 0;
}

三.欧拉序求法

支持在线。
时间复杂度O(nlogn),
每个询问的复杂度O(1)
#include<iostream>
#include<queue>
#include<cstdio>
#include<cmath>
#define ll long long
using namespace std;
int n,m,d[200005],prt[200005],cnt,tot,h[200005];
struct node{int to,next;}a[200005];
void add(int x,int y){cnt++;a[cnt].to=y;a[cnt].next=h[x];h[x]=cnt;}
int f[400005][20],g[400005][20];
int q[400005];
int td1[200005],td2[200005];
void dfs(int x,int deep)
{
    int i,j;
    d[x]=deep;
    q[++tot]=x;
    td1[x]=tot;td2[x]=tot;
    for(i=h[x];i;i=a[i].next)
    {
        j=a[i].to;
        dfs(j,deep+1);
        q[++tot]=x;td2[x]=tot;
    }
}
int ask(int L,int r)
{
    int k=log(r-L+1)/log(2);
    if(g[L][k]<g[r-(1<<k)+1][k])return f[L][k];
    else return f[r-(1<<k)+1][k];
}
int main()
{
    int i,j,k,x,y,root;
    scanf("%d%d",&n,&m);
    for(i=2;i<=n;++i)scanf("%d%d",&x,&y),add(x,y),prt[y]=x;
    for(i=1;i<=n;++i)if(!prt[i]){root=i;break;}
    dfs(root,1);
    for(i=1;i<=tot;i++)f[i][0]=q[i],g[i][0]=d[q[i]];
    for(j=1;(1<<j)<=tot;j++)
      for(i=1;i<=tot;i++)
      {
          if(g[i][j-1]<g[i+(1<<(j-1))][j-1])
            f[i][j]=f[i][j-1],g[i][j]=g[i][j-1];
          else f[i][j]=f[i+(1<<(j-1))][j-1],g[i][j]=g[i+(1<<(j-1))][j-1];
      }
    for(i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        if(td1[x]>td2[y])swap(x,y);
        printf("%d
",ask(td1[x],td2[y]));
    }    
    return 0;
}

 

 

以上是关于最近公共祖先LCA的求法的主要内容,如果未能解决你的问题,请参考以下文章

LCA 最近公共祖先

最近公共祖先 LCA

LCA的RMQ求法

LCA 最近公共祖先

二叉树最近公共祖先

二叉树最近公共祖先