Tarjan算法离线求LCA

题目

输入样例1:

2 2 
1 2 100 
1 2 
2 1

输出样例1: 

100
100

输入样例2:

3 2
1 2 10
3 1 15
1 2
3 2

输出样例2: 

10
25

LCA算法:

LCA(Least Common Ancestors)最近公共祖先

Tarjan 求 LCA 是一种离线的算法,也就是说它一遍求出所有需要求的点的 LCA,而不是需要求哪两个点再去求。

在深度优先遍历时,将所有点分成三大类:

[1]已经遍历过,且回溯过的点

[2]正在搜索的分支

[3]还未搜索到的点

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

#define x first
#define y second

using namespace std;

typedef pair<int,int> PII;
const int N = 10010,M = 2*N;
int n,m;
int h[N],e[M],w[M],ne[M],idx;//邻接表
int dist[N];//每个点和1号点的距离
int p[N];//并查集
int res[M];//存储答案
int st[N];//存储每个节点的状态
vector<PII> query[N];//存储每组询问
// query[i][first][second] 
//first存查询距离i的另外一个点j,second存查询编号idx

void add(int a,int b,int c)
{
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
}

int find(int x)
{
    if(p[x]!=x)
        p[x]=find(p[x]);
    return p[x];
}

void dfs(int u,int fa)
{
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(j==fa)
            continue;
        dist[j]=dist[u]+w[i];
        dfs(j,u);
    }
}

//st[u]==2已经遍历并且回溯了
//st[u]==1正在遍历
void tarjan(int u)
{
    st[u]=1;//当前路径点标记为1
    // u这条路上的根节点的左下的点用并查集合并到根节点
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!st[j])
        {
            tarjan(j);//往左下搜
            p[j]=u;//从左下回溯后把左下的点合并到根节点
        }
    }
    
    // 对于当前点u 搜索所有和u
    for(auto item:query[u])
    {
        int y=item.first,id=item.second;
        
        if(st[y]==2)//如果查询的这个点已经是左下的点
        {           //(已经搜索过且回溯过,标记为2)
            int anc=find(y);//y的根节点
            //y节点的p[y]已经回溯到公共祖先
            // x到y的距离 = d[x]+d[y] - 2*d[lca]
            res[id]=dist[u]+dist[y]-2*dist[anc];
            //第idx次查询的结果 res[idx]
        }
    }
    //点u已经搜索完且要回溯了 就把st[u]标记为2
    st[u]=2;
}
int main()
{
    cin>>n>>m;
    // 建图
    memset(h, -1, sizeof h);
    
    for(int i=0;i<n-1;i++)
    {
        int x,y,k;
        scanf("%d%d%d",&x,&y,&k);
        add(x,y,k);
        add(y,x,k);
    }
    
    // 存下询问
    for(int i=0;i<m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if(x!=y)
        {
            query[x].push_back({y,i});
            query[y].push_back({x,i});
        }
    }
    
    for(int i=1;i<=n;i++)
        p[i]=i;
    
    dfs(1,-1);
    tarjan(1);
    
    for(int i=0;i<m;i++)
        printf("%d\n",res[i]);//把每次询问的答案输出
        
    return 0;
}

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LCA(最近公共祖先)是指在一棵树中,找到两个节点的最近的共同祖先节点。而Tarjan算法是一种用于解强连通分量的算法,通常应用于有向图中。它基于深度优先搜索(DFS)的思想,通过遍历图中的节点来构建强连通分量。Tarjan算法也可以用于解LCA问题,在有向无环图(DAG)中。 具体来说,在使用Tarjan算法解LCA时,我们需要进行两次DFS遍历。首先,我们从根节点开始,遍历每个节点,并记录每个节点的深度(即从根节点到该节点的路径长度)。然后,我们再进行一次DFS遍历,但这次我们在遍历的过程中,同时进行LCA的查找。对于每个查询,我们将两个待查询节点放入一个查询列表中,并在遍历过程中记录每个节点的祖先节点。 在遍历的过程中,我们会遇到以下几种情况: 1. 如果当前节点已被访问过,说明已经找到了该节点的祖先节点,我们可以更新该节点及其所有后代节点的祖先节点。 2. 如果当前节点未被访问过,我们将其标记为已访问,并将其加入到查询列表中。 3. 如果当前节点有子节点,我们继续递归遍历子节点。 最终,对于每个查询,我们可以通过查询列表中的两个节点的最近公共祖先节点来解LCA。 需要注意的是,Tarjan算法的时间复杂度为O(V+E),其中V为节点数,E为边数。因此,对于大规模的树结构,Tarjan算法是一种高效的解LCA问题的方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值