LCA算法-ST在线算法

这篇文章讲LCA算法(Least Common Ancestor)。

LCA:顾名思义,指在一棵有根树中,距离两个结点u和v最近的公共祖先(换句话说,是离根节点最远的公共祖先)。
LCA算法:对于该问题,最容易想到的算法是分别从节点u和v回溯到根节点,获取u和v到根节点的路径P1,P2,其中P1和P2可以看成两条单链表,这就转换成常见问题:判断两个单链表是否相交,如果相交,给出相交的第一个点。该算法总的复杂度是O(n)(其中n是树节点个数)。但当数据量非常大且查询很频繁时,该算法无法在有效时间内查询出正解。所以本篇文章将介绍一种比较高效的算法解决该问题。在线算法(DFS+ST)
①DFS
举个例子。
例子图片
假设遍历顺序从右至左,则DFS遍历可得。
DFS遍历序列F_____________1 3 5 7 5 6 5 3 4 3 1 2
深度序列deep______________1 2 3 4 3 4 3 2 3 2 1 2
结点首次出现位置first ____1 12 2 9 3 6 4
对于查询两个结点的LCA就是各自首次出现位置间深度最小的结点。于是可以转化为RMQ问题,在一段区间中寻找最小值。所以再用ST算法解决该问题。
根据例题,如果询问LCA(4,7),就相当于RMQ(4,7)。first[4]=9,first[7]=4。在深度序列中区间(4,9)是4 3 4 3 2 3,而最近公共祖先的深度就是这段区间中最小的2。再将深度代入遍历序列得到LCA(4,7)=3。
②ST
令F[i,j]为从下标i开始,长度为2^j的元素的最小值。那么状态转移方程就是F[i,j]=min(F[i,j-1],F[i+2^(j-1),j-1])。这个式子在这里有详细解释哦!
③查询
假如要查询[m,n]的最小值,那么先求出一个最大的k。使k满足2^k<=(n-m+1)。于是我们可以将[m,n]分成两个(部分重叠的)长度为2^k的区间:[m,m+2^k-1],[n-2^k+1,n];F[m,k]为F[m,m+2^k-1]的最小值,F[n-2^k+1,k]是[n-2^k+1,n]的最小值。状态转移方程:RMQ(i,j)=min(F[m,k],F[n-2^k+1,k]);
接下来看一下例题&代码。

LZOI2225 最近公共祖先

题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入
第一行包含三个正整数n, q, s,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来n - 1行每行包含两个正整数u, v,表示u结点和v结点只见有一条直接连接的边(数据保证可以构成树)。
接下来q行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出
输出包含q行,每行包含一个正整数,依次为每一个询问的结果。

//相信看了详解后都能理解了吧! 
#include<bits/stdc++.h>
using namespace std;
int n,q,root,u,v,a,b,F[50001],f[50001][21],first[50001],deep[50001],head[50001],tot;
bool vis[50001];
struct E
{
    int v;
    int u;
    E(){u=0;}
}e[1000001];
int en=0;
void dfs(int u,int dep)
{
    vis[u]=1;
    F[++tot]=u;
    first[u]=tot;
    deep[tot]=dep;
    for(int i=head[u];i!=0;i=e[i].u)
        if(!vis[e[i].v])
        {
            dfs(e[i].v,dep+1);
            F[++tot]=u;
            deep[tot]=dep;
        }
}
void ST(int len)
{
    for(int i=1;i<=len;i++)f[i][0]=i;
    for(int j=1;(1<<j)<=len;j++)
    {
        for(int i=1;i+(1<<j)-1<=len;i++)
        {
            int a=f[i][j-1],b=f[i+(1<<(j-1))][j-1];
            f[i][j]=deep[a]<deep[b]?a:b;
        }
    }
}
int RMQ(int l,int r)
{
    int k=0;
    while((1<<(k+1))<r-l+1)k++;
    int a=f[l][k],b=f[r-(1<<k)+1][k];
    return deep[a]<deep[b]?a:b;
}
int LCA(int u,int v)
{
    int x=first[u],y=first[v];
    if(x>y)swap(x,y);
    int res=RMQ(x,y);
    return F[res];
}
void add(int u,int v)//这里用到了邻接表存储哦!
{
    en++;
    e[en].v=v;
    e[en].u=head[u];
    head[u]=en;
}
int main()
{
    cin>>n>>q>>root;
    for(int i=1;i<n;i++)
    {
        cin>>u>>v;
        add(u,v);
        add(v,u);
    }
    dfs(root,1);
    ST(tot);
    for(int i=1;i<=q;i++)
    {
        cin>>a>>b;
        cout<<LCA(a,b)<<endl;
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值