详谈LCA 在线(RMQ-ST) 和 离线(Tarjan)hdu2586为例

版权声明:编写不易,转载请注明出处,谢谢。 https://blog.csdn.net/WilliamSun0122/article/details/77912069

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。

参考博客:
http://www.cnblogs.com/scau20110726/archive/2013/05/26/3100812.html

离线

LCA的离线算法一般是基于搜索的Tarjan算法。

思想:
Tarjan是一个离线算法,先把所有的询问保存下来,然后开始Tarjan搜索。从根节点开始形成一颗深搜树,从任意节点u出发,遍历其所有子节点。这样在回溯u时其所有子节点已经遍历,这时再把u放入合并集合中,这样u节点和所有之前遍历的u节点的子树枝节点的lca就是u,然后在处理关于u的询问。这样在Tarjan的一遍深搜之后就已经把所有询问的答案保存下来,最后按询问顺序输出即可。

伪代码

vis[i] //i节点是否遍历过
Find(i) //并查集中找到i的祖先
f[i] //i节点的祖先节点

void Tarjan(int u)
{
    vis[u] = true;  //置u节点为已遍历
    f[u]=u;  //初始u节点的祖先节点为u
    for(u的所有儿子v)
        if(该儿子v没有被访问)
        {
            Tarjan(v);
            Union(u,v); //将u的子节点v的祖先置为u
        }
    for(包含u的所有询问(u,v))
        if( v遍历过 )  //说明v和u是同一节点(可能是u或v)的子节点
            LCA(u,v) = LCA(v,u) = Find(v); 
}

假设在询问中u,v是同一节点t的子节点
那么在回溯到u处理关于u的询问的时候,v一定已经完成了Tarjan(说明已经将v的祖先节点置为t),所以LCA(u,v)会等于t。
假设在询问中u,v是同一节点u的子节点
同理在回溯到u处理关于u的询问的时候,v一定已经完成了Tarjan(说明已经将v的祖先节点置为u),所以LCA(u,v)会等于u。
假设在询问中u,v是同一节点v的子节点
同理在回溯到u处理关于u的询问的时候,v的祖先节点都还未完成其子节点v的Tarjan(说明此时v的祖先节点仍为v),所以LCA(u,v)会等于v。

可以自己画棵树照上面的伪代码体会一下。

在线

LCA的在线算法就是转化成RMQ问题然后用ST算法解决。
这里问题的关键就是怎么把LCA转化成RMQ问题。其实就是通过dfs,然后用数组记录节点出现的顺序,深度以及每个节点第一次出现的位置,之后就转化成RMQ问题求解。


图基本已经说的很清楚了,但它用一个pos数组保存最小值下标,其实这是不必要的。因为我们现在要求的就是最小值对应的下标,所以我们可以直接用dp数组保存下标,然后转移下标即可。
看看代码就明白了。

void ST(int len)
{
    for(int i=1;i<=len;i++) dp[i][0]=i;
    int k = (int)(log((double)len)/log(2.0));
    for(int j=1;j<=k;j++)
    {
        for(int i=1;i+(1<<j)-1<=len;i++)
        {
            int a = dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
            dp[i][j] = deep[a]<deep[b] ? a : b;
        }
    }
}

int RMQ(int l,int r)
{
    int k = (int)(log((double)(r-l+1))/log(2.0));
    int a=dp[l][k],b=dp[r-(1<<k)+1][k];
    return deep[a]<deep[b] ? a : b;
}

hdu2586

题意
给定一棵树,每条边都有一定的权值,m次询问,每次询问某两点间的距离。

扫描二维码关注公众号,回复: 3233256 查看本文章

题解
可以转化成LCA来解。首先找到u, v 两点的lca,然后计算一下距离值就可以了。这里的计算方法是,记下根结点到任意一点的距离dis[],这样ans = dis[u] + dis[v] - 2 * dis[lca(v, v)]。

在线(RMQ-ST)代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 4e4+5;
int n,m;
int dp[maxn*2][20];
int p[maxn*2],deep[maxn*2],first[maxn],dis[maxn];
bool vis[maxn];

struct node{
    int to,w,next;
}edge[maxn*2];
int tot,head[maxn];

void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
    memset(vis,false,sizeof(vis));
    memset(dis,0,sizeof(dis));
}

void add_edge(int u,int v,int w)
{
    edge[tot].to = v;
    edge[tot].w = w;
    edge[tot].next = head[u];
    head[u] = tot++;
    edge[tot].to = u;
    edge[tot].w = w;
    edge[tot].next = head[v];
    head[v] = tot++;
}

void dfs(int u,int dep)
{
    p[++tot]=u;
    first[u]=tot;
    deep[tot]=dep;
    vis[u]=true;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v = edge[i].to;
        if(!vis[v])
        {
            int w = edge[i].w;
            dis[v] = dis[u]+w;
            dfs(v,dep+1);
            p[++tot]=u;
            deep[tot]=dep;
        }
    }
}

void ST(int len)
{
    for(int i=1;i<=len;i++) dp[i][0]=i;
    int k = (int)(log((double)len)/log(2.0));
    for(int j=1;j<=k;j++)
    {
        for(int i=1;i+(1<<j)-1<=len;i++)
        {
            int a = dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
            dp[i][j] = deep[a]<deep[b] ? a : b;
        }
    }
}

int RMQ(int l,int r)
{
    int k = (int)(log((double)(r-l+1))/log(2.0));
    int a=dp[l][k],b=dp[r-(1<<k)+1][k];
    return deep[a]<deep[b] ? a : b;
}

int LCA(int u,int v)
{
    int l=first[u],r=first[v];
    if(l>r) swap(l,r);
    return p[RMQ(l,r)];
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        init();
        int u,v,w;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            add_edge(u,v,w);
        }
        tot=0;
        dfs(1,1);
        ST(tot);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&u,&v);
            printf("%d\n",dis[u]+dis[v]-2*dis[LCA(u,v)]);
        }
    }
    return 0;
}

离线(Tarjan)代码

#include <bits/stdc++.h>
using namespace std;

const int N=40005,M=205;
int vis[N],root[N],dis[N],ans[M][3],n,m,num1,num2;
struct node{
    int v,d;
    node *next;
};
node *link1[N],*link2[N],edg1[N*2],edg2[N*2];

void add(int a,int b,int c,node *link[],node edg[],int &num)
{
    edg[num].v=a;
    edg[num].d=c;
    edg[num].next=link[b];
    link[b]=edg+num++;
    edg[num].v=b;
    edg[num].d=c;
    edg[num].next=link[a];
    link[a]=edg+num++;
}

int find_root(int a)
{
    return a==root[a] ? a : root[a]=find_root(root[a]);
}

void munion(int a,int b)
{
    a = find_root(a);
    b = find_root(b);
    if(a==b) return;
    root[b]=a;
}

void init()
{
    num1=num2=0;
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    memset(link1,0,sizeof(link1));
    memset(link2,0,sizeof(link2));
}

void Tarjan(int x)
{
    vis[x]=1;
    root[x]=x;
    for(node *p=link1[x];p;p = p->next)
    {
        if(!vis[p->v])
        {
            dis[p->v]=dis[x]+p->d;
            Tarjan(p->v);
            munion(x,p->v);
        }
    }
    for(node *p=link2[x];p;p=p->next)
    {
        if(vis[p->v])
        {
            ans[p->d][2]=find_root(p->v);
        }
    }
}

int main()
{
    int T,a,b,c;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        init();
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c,link1,edg1,num1);
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            add(a,b,i,link2,edg2,num2);
            ans[i][0]=a;ans[i][1]=b;
        }
        Tarjan(1);
        for(int i=1;i<=m;i++) printf("%d\n",dis[ans[i][0]]+dis[ans[i][1]]-2*dis[ans[i][2]]);
    }
    return 0;
}

比较(上面是在线,下面是离线)

猜你喜欢

转载自blog.csdn.net/WilliamSun0122/article/details/77912069