lca

Posted liuwenyao

tags:

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

http://www.cnblogs.com/zhouzhendong/p/7256007.html

LCA  最近公共祖先,只在同一个树中距离两个子节点最近的那个父节点。

在网上最快的有两种算法分别是算法发明家——Tarjan 以他名字发明的算法,和倍增算法,和树剖。

而Tarjan算法几乎只能求两个点之间的最近公共祖先,因此我们不介绍,而倍增算法便是通过倍增不让每一个子节点一步步地向上找,而是跳着找。

这样肯定比一般的暴力寻找快,而应该怎么实现呢,我们应用一个邻接表存图,用来处理每条边的边权用来应对某些题。

对于这个算法,我们从最暴力的算法开始:

    ①如果aa和bb深度不同,先把深度调浅,使他变得和浅的那个一样

    ②现在已经保证了aa和bb的深度一样,所以我们只要把两个一起一步一步往上移动,直到他们到达同一个节点,也就是他们的最近公共祖先了。

 而实际上,一步一步往上移动太慢,我们可以做一个预处理:

  fai,jfai,j表示节点ii往上走2j2j次所到达的祖先,那么不难写出转移式:

      fai,0=fatheri,fai,j=fafai,j1,j1fai,0=fatheri,fai,j=fafai,j−1,j−1

  然后在求LCALCA的时候,有这样一个性质:(假设aa和bb深度一样)

  设anstx,yanstx,y为节点x网上走y步到达的祖先,对于一个kk,如果ansta,k=anstb,kansta,k=anstb,k,那么对于所有k(k>k)k′(k′>k),一定有ansta,k=anstb,kansta,k′=anstb,k′;对于一个kk,如果ansta,kanstb,kansta,k≠anstb,k,那么对于所有k(k<k)k′(k′<k),一定有ansta,kanstb,kansta,k′≠anstb,k′,而且LCA(a,b)=LCA(ansta,k,anstb,k)LCA(a,b)=LCA(ansta,k,anstb,k)。
于是求法就渐渐的现行了:

  1. 把aa和bb移到同一深度(设depthxdepthx为节点xx的深度),假设depthadepthbdeptha≤depthb,所以我们的目的是把bb向上移动i=(depthbdeptha)i=(depthb−deptha)层,那么,由于之前有预处理的fafa数组,我们把ii写成二进制形势,然后利用fafa数组来在O(logn)O(log?n)的复杂度中完成;

  2. 寻找aa和bb的LCALCA下一层的两个祖先。利用之前的那个性质,再利用倍增,如果aa和bb的第2k2k个祖先不是同一个,那么把aa改为faa,kfaa,k,bb改为fab,kfab,k,kk减11;否则直接kk减11;当然在这之前要实现确定kk的最大值,从大往小处理下去。最终的结果就是faa,0faa,0或者fab,0fab,0。

  注意,如果aa和bb在调节深度之后已经是同一个祖先的,那么直接返回aa或者bb。

洛谷LCA模板代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxlog=20;
const int maxn=550000;
int n,m,s;
int root;
int fa[maxn][maxlog];
int deep[maxn];
int lin[1000010],tot;
struct cym{
    int next,to;
}e[2*maxn];
void add(int u,int v)
{
    e[++tot].to=v;
    e[tot].next=lin[u];
    lin[u]=tot;
} 
void dfs(int u,int father,int d)
{
    fa[u][0]=father;
    deep[u]=d;
    for(int i=lin[u];i!=-1;i=e[i].next)
      if(e[i].to!=father)
      dfs(e[i].to,u,d+1);
}
void init()
{
    dfs(root,-1,0);
    for(int k=0;k+1<maxlog;k++)
    {
        for(int v=1;v<=n;v++)
        if(fa[v][k]<0)
        fa[v][k+1]=-1;
        else
        fa[v][k+1]=fa[fa[v][k]][k];
    }
}
int lca(int u,int v)
{
    if(deep[u]>deep[v])//如果u在上面,交换 
    swap(u,v);
    for(int k=0;k<maxlog;k++)
    {
        if((deep[v]-deep[u])>>k&1)
        v=fa[v][k];
    }
    if(u==v)
    return u;
    for(int k=maxlog-1;k>=0;k--)
    {
        if(fa[v][k]!=fa[u][k])
        {
            u=fa[u][k];
            v=fa[v][k];
        }
    }
    return fa[u][0];
}
int main()
{
    memset(lin,-1,sizeof(lin));
    int a,b;
    scanf("%d%d%d",&n,&m,&root);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
     } 
     init();
     for(int i=1;i<=m;i++)
     {
         int u,v,a;
         scanf("%d%d",&u,&v);
         a=lca(u,v);
         printf("%d
",a); 
     }
     return 0;
}

 

以上是关于lca的主要内容,如果未能解决你的问题,请参考以下文章

代码源 Div1 - 105#451. Dis(倍增求LCA)

浅谈LCA

代码源 Div1 - 105#451. Dis(倍增求LCA)

LCA

二叉树---最近公共父节点(LCA)

LCA 最近公共祖先