LCA 在线离线算法 笔记

Posted 为了更优秀的你,加油!

tags:

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

参考链接:

http://dongxicheng.org/structure/lca-rmq/

http://blog.csdn.net/y990041769/article/details/40887469

http://www.cnblogs.com/JVxie/p/4854719.html

http://m.blog.csdn.net/qq_35935435/article/details/54916022

Tarjan离线算法:

什么是Tarjan(离线)算法呢?顾名思义,就是在一次遍历中把所有询问一次性解决,所以其时间复杂度是O(n+q)

    Tarjan算法的优点在于相对稳定,时间复杂度也比较居中,也很容易理解。

    下面详细介绍一下Tarjan算法的基本思路:

      1.任选一个点为根节点,从根节点开始。

      2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。

      3.若是v还有子节点,返回2,否则下一步。

      4.合并v到u上。

      5.寻找与当前点u有询问关系的点v。

      6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。

    遍历的话需要用到dfs来遍历,合并就是利用并查集来合并两个节点。

    伪代码如下:  

Tarjan(u)//marge和find为并查集合并函数和查找函数
{
    for each(u,v)    //访问所有u子节点v
    {
        Tarjan(v);        //继续往下遍历
        marge(u,v);    //合并v到u上
        标记v被访问过;
    }
    for each(u,e)    //访问所有和u有询问关系的e
    {
        如果e被访问过;
        u,e的最近公共祖先为find(e);
    }
}

RMQ区间最值询问算法:ST(Sparse Table)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

在线算法(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<iostream>
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;
}

 

 

 

以上是关于LCA 在线离线算法 笔记的主要内容,如果未能解决你的问题,请参考以下文章

Tarjan之求LCA

『图论』LCA 最近公共祖先

『图论』LCA最近公共祖先

最近公共祖先

tarjan算法求最近公共祖先

LCA最近公共祖先在线离线