LCA 离线Tarjan算法和在线倍增算法

LCA是很早之前听说过的,但是一直没学,看到一个网络流的题目设计到LCA所以今天就学习了一下。

求两个节点的公共祖先(即求深度最深的祖先)

1.tarjan算法

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define N 10005

int in[N],vis[N],fa[N];
int flag,n,qu,qv;
vector<int>V[N];
void init()
{
    for(int i=1;i<=n;i++)
    {
        V[i].clear();
        in[i]=0;
        fa[i]=i;
        vis[i]=0;
    }
    flag=0;
}

int Find(int x)
{
    return fa[x]==x?x:fa[x]=Find(fa[x]);
}
void U(int x,int y)
{
    int f1=Find(x);
    int f2=Find(y);
    fa[f1]=f2;
}

void Tarjan(int u)
{
    //cout<<u<<endl;
    if(flag)return ;
    for(int i=0;i<V[u].size();i++)
    {
        int v=V[u][i];
        Tarjan(v);
        U(v,u);
        vis[v]=1;
    }
    if(flag) return ;
    if(u==qu && vis[qv])
    {
        printf("%d\n",Find(qv));
        flag=1;
    }
    if(u==qv && vis[qu])
    {
        printf("%d\n",Find(qu));
        flag=1;
    }
    return ;
}

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        scanf("%d",&n);
        init();
        for(int i=1;i<=n-1;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            in[v]++;
            V[u].push_back(v);
        }
        scanf("%d%d",&qu,&qv);
        for(int i=1;i<=n;i++)
        {
            if(in[i]==0)
            {
                Tarjan(i);
                break;
            }
        }
    }
}

离线的tarjan算法是利用了dfs+并查集

1.先对树进行深度便利

2.如果当前节点没有子节点则返回

3.标记当前节点,并且把当前节点和他的父节点放进一个集合中

4.遍历包含该点的问题,如果另一个点被标记的话则最近公共祖先就是当前节点的父节点(这里指已经压缩过路径的父节点)。

 

2.在线的倍增算法

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define N 500005

struct node
{
    int to,nt;
}g[N*2];

int tot;
int head[N];
void addedg(int x,int y)
{
    g[tot].to=y;
    g[tot].nt=head[x];
    head[x]=tot++;

    g[tot].to=x;
    g[tot].nt=head[y];
    head[y]=tot++;
}

int dep[N];
int f[N][21];

void dfs(int u,int fa)
{
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=head[u];i!=-1;i=g[i].nt)
    {
        int to=g[i].to;
        if(to!=fa)
            dfs(to,u);
    }
}

int Lca(int a,int b)
{
    if(dep[a]<dep[b])swap(a,b);
    for(int i=20;i>=0;i--)
    {
        if(dep[a]-(1<<i)>=dep[b])
        {
            a=f[a][i];
        }
    }
    if(a==b)return a;
    for(int i=20;i>=0;i--)
    {
        if(f[a][i]!=f[b][i])
        {
            a=f[a][i];
            b=f[b][i];
        }
    }
    return f[a][0];
}
int main()
{
    int n,m,s;
    cin>>n>>m>>s;
    memset(head,-1,sizeof(head));
    int x,y;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        addedg(x,y);
    }
    dfs(s,0);
    for(int i=1;i<=20;i++)
    {
        for(int j=1;j<=n;j++)
        {
            int p=f[j][i-1];
            f[j][i]=f[p][i-1];
        }
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",Lca(x,y));
    }
    return 0;
}

算法流程:

1.首先对当前的树进行深度优先遍历并且给每个节点标上深度并且记录当前节点往上走2的i次幂时的父节点

2.注意遍历双向边的树时不要死循环。

3.利用上边的到的已知信息(即每个节点向上走深度为1时的父节点),我们利用倍增的思想得到当前节点向上走2的i(1-n)时的父节点。

4.即f[a][i]=f[f[a][i-1]][i-1];

5.然后利用这些信息我们就可以在logn的时间内找到最近公共祖先。

6.我们先让a点达到与b点同样的深度(这里假设a的深度大于b的深度),这个过程利用的性质是任何一个数(这里是深度差)

可以由若干个不同的2的i次幂组成(需注意的是要倒着来)。

7.达到同一深度之后就继续利用刚才所说的性质 (其实还是慢慢找到父节点相同的点)。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值