How far away ?(LCA Tarjan算法)

2 篇文章 0 订阅

题目传送门:HDU-2586
这道题题意很简单,给你了n个村子,然后给了n-1条带权值的边,(就等于说给了你一棵树,m次询问,问你x道y的最小距离是多少。
很明显就是让求两个点的lLCA,(倍增法还是很好用的),今天准备练习一下用Tarjan算法求一下LCA,就拿这道题来练练手了。
Trajan求LCA算法是一个离线算法 O ( N ∗ M ) O(N*M) O(NM),时间复杂度可能要比倍增法 ( O ( N ∗ M ∗ l o g N ) ) (O(N*M*logN)) O(NMlogN)要更优那么一丢丢,我感觉其实感觉差别并不大 (只求不要打脸)
说一下这个算法的大致思路吧
1,我们DFS这整颗树,DFS遍历的过程中,我们把结点分为三类,用vis[]数组标记
vis[i]=2说明这个结点已经遍历过并且回溯过了。
vis[i]=1说明这个点遍历过但是还没有回溯。
vis[i]=1说明这个点还没有被遍历。
这样做的好处是,因为DFS遍历的特点,我们可以知道,对于正在访问的结点i,那么从根节点到当前结点i的路径都已经被标记为1,(换句话说就是i的祖先都被标记为1)如果j是已经是回溯过的点,那么LCA(i,j)就是j往上找到vis[]为1的结点,(这一步我们可以用并查集实现,把vis[j]=2的点所在集合附属到前面vis[]为1的集合中,具体可以看代码实现)。
因为是离线算法,用vector记录样例序号。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<vector>
#include<math.h>
#include<bits/stdc++.h>
#include<map>
using namespace std;
#define LL long long
double eps=1e-10;
const LL N=1e5+10;
struct zxc
{
    int to,net,w;
}e[N*2];
int head[N];
int ans[250];
int val[N];
int vis[N];
int fa[N];
int tot=1;
int n,m;
vector<int>q[N],id[N];
void init()
{
    tot=1;
    memset(head,-1,sizeof(head));
    memset(vis ,0,sizeof(vis));
    for(int i=0;i<=n;i++)
    {
        fa[i]=i;
        q[i].clear();
        id[i].clear();
    }
}
int fin(int x)
{
    if(fa[x]!=x)
    {
        return fa[x]=fin(fa[x]);
    }
    return fa[x];
}
void add(int u,int v,int w)
{
    e[tot]={v,head[u],w};
    head[u]=tot++;
    e[tot]={u,head[v],w};
    head[v]=tot++;
}
void dfs(int x)
{
   vis[x]=1;
    for(int i=head[x];i!=-1;i=e[i].net)
    {
        int y=e[i].to;
        //printf("%d**\n",y);
        if(vis[y])continue;
        val[y]=val[x]+e[i].w;
        dfs(y);
        fa[y]=x;
    }
    for(int i=0;i<q[x].size();i++)
    {
        int idd=id[x][i];
        int z=q[x][i];
        if(vis[z]==2)
        {
            int w=fin(z);
            ans[idd]=min(ans[idd],val[x]+val[z]-2*val[w]);
        }
    }
    vis[x]=2;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        init();
        int x,y,z;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            if(x==y)
            {
                ans[i]=0;
            }
            else
            {
                q[x].push_back(y);
            id[x].push_back(i);
            q[y].push_back(x);
            id[y].push_back(i);
            ans[i]=0x3f3f3f3f;
            }


        }
        dfs(1);
        for(int i=1;i<=m;i++)
        {
            printf("%d\n",ans[i]);
        }
    }
    return 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、付费专栏及课程。

余额充值