两种lca的求法:树上倍增,tarjan

Posted zsben991126

tags:

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

第一种:树上倍增

  f[x,k]表示x的2^k辈祖先,即x向根结点走2^k步达到的结点。

  初始条件:f[x][0]=fa[x]

  递推式:f[x][k]=f[ f[x][k-1] ][k-1]

一次bfs预处理f数组(nlogn),然后每次询问都可以在(logn)时间内求出x,y的lca

求lca的步骤

  1.令x的深度大于y,然后通过二进制拆分将x上调到与y同一个深度(依次用k=2^logn,...2^1,2^0试探)

  2.如果此时x==y,那么y=lca(x,y),算法结束

  3.继续用第一步的二进制拆分法同时将x,y往上提,保持深度一致,并且两者不可以相会

  4.最后f[x][0]=f[y][0]=lca(x,y)

注意第三步,x,y不可以相会!

int t=(int)(log(n)/log(2))+1;
void bfs(){//预处理算法,求出f数组,深度数组d 
    q.push(1);d[1]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i!=-1;i=edge[i].nxt){
            int v=edge[i].to;
            if(d[v])continue;
            d[v]=d[u]+1;
            f[v][0]=u;
            for(int k=1;k<=t;k++)
                f[v][k]=f[f[u][k-1]][k-1];
            q.push(v);
        }
    }
}
int lca(int x,int y){
    if(d[x]>d[y])swap(x,y);
    for(int i=t;i>=0;i--)//把x提到y的深度 
        if(d[f[x][i]]>=f[y])x=f[x][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]; 
}

第二种,tarjan+并查集优化,离线算法

首先说明最简单的向上标记法:x,y沿着边不停往上寻找lca,直到相遇,相遇点就是lca

使用并查集对“向上标记法”进行优化

  在tarjan算法的dfs过程中,所有点分为三类

  1.没有被访问到的点。这类点被标记0

  2.正处于dfs阶段,即没有经过回溯的点,这类点给标记1

  3.已经结束dfs阶段,即已经回溯结束的点,这类点给标记2

每次一个点结束dfs阶段,就将其标记位2,并将这个点合并到其父亲所在的并查集中(可以保证其父亲此时的标记一定1),

当其他点有问题边连到该点时,这个问题的解就是其所在并查集的标号

比较形象化的理解,在dfs过程中,x,y结点的lca必定是其路径上深度最小的结点,而深度最小的结点在dfs访问到y时一定没有被回溯,并且此时x已经被回溯了,被归并在lca所在的集合中,

那么扫描和y有关的所有问题,如果另一个点已经被标记位2,说明y和这个点已经相遇过了,相遇的点就是这个点所在的并查集标号

那么就使用并查集来优化这个集合的合并即可。

//多次求树上两点之间的距离 
vector<int>q[maxn],q_id[maxn];//查询链表 
int v[maxn],f[maxn],ans[maxn];//标记数组,并查集 
void tarjan(int x){
    v[x]=1;
    for(int i=head[x];i!=-1;i=edge[i].nxt){
        int y=edge[i].to
        if(v[y])continue;
        d[y]=d[x]+edge[i].w;//求y的深度 
        tarjan(y);
        f[y]=x;//回溯后将y并入x集合中 
    } 
    for(int i=0;i<q[x].size();i++){
        int y=q[x][i],id=q_id[x][i];
        if(v[y]==2){//y是已经回溯过的结点
            int lca=find(y);//使用并查集求y的集合标记
            ans[id]=min(ans[id],d[x]+d[y]-2*d[lca]);
        } 
    } 
    v[x]=2;//x回溯完成了 
}

 

以上是关于两种lca的求法:树上倍增,tarjan的主要内容,如果未能解决你的问题,请参考以下文章

最近在写一些树上的东西,先发一波LCA的吧!

[luogu3379]最近公共祖先(树上倍增求LCA)

51nod2621 树上距离一题四解ST表+倍增+Tarjan+树剖

最近公共祖先(LCA)---tarjan算法

最近公共祖先(LCA)问题

浅谈LCA