tarjan离线、倍增在线求LCA

一、何为在线与离线?

       在线算法:指它可以以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入。

       离线算法:在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果。

                         例如,选择排序在排序前就需要知道所有待排序元素,然而插入排序就不必。

       因为在线算法并不知道整个的输入,所以它被迫做出的选择最后可能会被证明不是最优的,对在线算法的研究主要集中在当前环境下怎么做出选择。

二、tarjan算法:

       LCA:两个点在这棵树上距离最近的公共祖先节点

       Tarjan(离线)算法:把要求的值存下来,就是所谓的离线一下,,然后结合并查集深搜点,如果所求的两个点都vis[]==1那么输出他们的father。在一次遍历中把所有询问一次性解决,所以其时间复杂度是O(n+q),该算法优点在于相对稳定,时间复杂度也比较居中,也很容易理解。

三、tarjan算法步骤:

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

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

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

      4.用并查集来合并v到u上。

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

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

四、程序代码:

#include<bits/stdc++.h>
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]++;   //入度+1 
            V[u].push_back(v);
        }
        scanf("%d%d",&qu,&qv);
        for(int i=1;i<=n;i++)
        {
            if(in[i]==0)
            {
                Tarjan(i);
                break;
            }
        }
    }
}

五、倍增求LCA算法思想

       先让它们深度相同,然后倍增向上跳跃(每次跳2的i次方 步,是一种二进制的思想),求出LCA。

六、算法步骤:

      1、预处理求出所有结点倍增能达到的点。

      2、把询问的两个点移到同一深度。

      3、再一步一步求出LCA。

七、算法代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,s,num=0,head[1000001],dep[1000001],f[1000001][23];
int a1,a2;
struct edg
{
   int next,to;
}edge[1000001];

void edge_add(int u,int v)//链式前向星存图 
{
    num++;
    edge[num].next=head[u];edge[num].to=v;head[u]=num;
    edge[++num].next=head[v];edge[num].to=u;head[v]=num;
}

void dfs(int u,int father)//对应深搜预处理f倍增达到的点的数组 
{
    dep[u]=dep[father]+1;
    for(int i=1;(1<<i)<=dep[u];i++)
    {
        f[u][i]=f[f[u][i-1]][i-1];
    }
    for(int i=head[u];i;i=edge[i].next)//遍历所有儿子 
    {
        int v=edge[i].to;
        if(v==father)continue;//双向图需要判断是不是父亲节点 
        f[v][0]=u;
        dfs(v,u);
    }
}

int lca(int x,int y)
{
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--)//从大到小枚举使x和y到了同一层 
    {
        if(dep[f[x][i]]>=dep[y])x=f[x][i];
        if(x==y)return x;
    }
    for(int i=20;i>=0;i--)//从大到小枚举 
    {
        if(f[x][i]!=f[y][i])//尽可能接近 
        {
            x=f[x][i];y=f[y][i];
        } 
    } 
    return f[x][0];//随便找一个**输出 
}

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<n;i++)
    {
        scanf("%d",&a1);scanf("%d",&a2);
        edge_add(a1,a2);//链式存边 
    }
    dfs(s,0);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&a1,&a2);
        printf("%d\n",lca(a1,a2));//求两个节点的LCA 
    }
}

八、倍增时间复杂度为O(nlogn+qlogn)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值