LCA Tarjan实现

Tarjan LCA

以前认为Tarjan算法只是用来解强连通分量的算法,后来发现LCA也有一个Tarjan算法。
下面借一个树来了解一下算法思路。(树已经横向放置)
询问组:
11 9
10 8
6 7
2 3
这里写图片描述
这个算法最好亲手模拟一遍,然后就会感到茅塞顿开。
先从1节点开始DFS深度优先遍历(左节点优先)。
1->2->4->8->10。
发现10没有子节点,找所有关于10的询问,发现询问组有一个10和8的询问,检查发现vis[8]=0,说明8节点并没有被遍历完所有边或者根本就没有遍历过8节点,这样的话我们就不管它。
然后就把10标记上vis[10]=1,然后利用并查集,把它的(直接)父节点记录下来fa[10]=8。
然后就继续回退到8的下一个子节点,找到11,同理,检查询问组发现有11和9的询问,且vis[9]=0,跳过,vis[11]=1,fa[11]=8,回退至8。
8没有子节点可遍历了,检查询问组发现有关于8的询问10和8,而且vis[10]=1,那么我们就返回值fa[10]=8,10和8的LCA就是8,没有其余的关于8的询问了,vis[8]=1,fa[8]=4,回退至4。
4没有子节点可遍历了,检查询问组发现没有关于4的询问,那么就vis[4]=1,fa[4]=2,回退至2。
继续到5和9,9没有子节点可遍历了,检查询问组发现有关于9的询问8和9,而且vis[8]=1,那么我们就返回值findd(fa[8])=2,9和8的LCA就是2,那么就vis[9]=1,fa[9]=5,回退至5。
回退到5,没有询问,标记,回退。
2没有子节点可遍历了,检查询问组发现有关于2的询问2和3,且vis[3]=0,跳过,vis[2]=1,fa[2]=1,回退至1。
1有子节点可遍历,遍历1->3->6。
6没有子节点可遍历了,检查询问组发现有关于2的询问6和7,且vis[7]=0,跳过,vis[6]=1,fa[6]=3,回退至3。
1有子节点可遍历,遍历3->7。
7没有子节点可遍历了,检查询问组发现有关于7的询问6和7,且vis[6]=1,那么我们就返回值fa[6]=3,6和7的LCA就是3,没有其余的关于7的询问了,vis[7]=1,fa[7]=3,回退至3。
3没有子节点可遍历了,检查询问组发现有关于2的询问2和3,且vis[2]=1,那么我们就返回值fa[2]=1,2和3的LCA就是1,没有其余的关于3的询问了,vis[3]=1,fa[3]=1,回退至1。
1没有子节点可遍历了,检查询问组发现没有关于1的询问,结束算法。

鸣谢:yangbowen2同学的纠正!

代码实现比较简单。
代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#define MAXX 500001
using namespace std;

int temp=0,i,j,m,n,s;
int tree_hd[MAXX],tree_y[MAXX*2],tree_nxt[MAXX*2];
int ask_hd[MAXX],ask_y[MAXX*2],ask_nxt[MAXX*2];
int ask_xx[MAXX],ask_yy[MAXX];
int fa[MAXX],deep[MAXX],vis[MAXX];
int ans[MAXX*4],ans_b[MAXX];

int r()
{
    int aans=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        aans*=10;
        aans+=ch-'0';
        ch=getchar();
    }
    return aans*f;
}

void tree_add(int xx,int yy)
{
    tree_y[++temp]=yy;
    tree_nxt[temp]=tree_hd[xx];
    tree_hd[xx]=temp;
}

void ask_add(int xx,int yy)
{
    ask_y[++temp]=yy;
    ask_nxt[temp]=ask_hd[xx];
    ask_hd[xx]=temp;
}

int findd(int fdx)
{
    if(fa[fdx]==fdx) return fdx;
    fa[fdx]=findd(fa[fdx]);
    return fa[fdx];
}

void dfs(int x)
{
    int son;
    for(int p=tree_hd[x];p;p=tree_nxt[p])
    {
        son=tree_y[p];
        if(!deep[son])
        {
            deep[son]=deep[x]+1;
            dfs(son);
            fa[son]=x;
            vis[son]=1;
        }
    }
    for(int qq=ask_hd[x];qq;qq=ask_nxt[qq])
    {
        if(vis[ask_y[qq]])
        {
            ans[qq]=findd(ask_y[qq]);
        }
    }
}

void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}

int main()
{
    freopen("lca.in","r",stdin);
    freopen("lca.out","w",stdout);
    n=r(),m=r(),s=r();
    int xx,yy;
    for(i=1; i<n; i++)
    {
        xx=r(),yy=r();
        tree_add(xx,yy);
        tree_add(yy,xx);
    }

    for(i=1;i<=n;i++)
    {
        fa[i]=i;
    }

    for(j=1; j<=m; j++)
    {
        ask_xx[j]=r(),ask_yy[j]=r();
        ask_add(ask_xx[j],ask_yy[j]);
        ask_add(ask_yy[j],ask_xx[j]);
    }
    deep[s]=1;
    dfs(s);

    for(i=1;i<=m;i++)
    {
        if(ans[i*2-1])
        write(ans[i*2-1]),putchar(10);
        else if(ans[i*2])
        write(ans[i*2]),putchar(10);
        else
        m++;
    }

}

这里写图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值