hdu 2586 LCA离线算法 Tarjan+并查集 O(n+q)

/*
第一次写最近公共祖先问题,用的邻接表指针。

对于一棵有根树,就会有父亲结点,祖先结点,当然最近公共祖先就是这两个点所有的祖先结点中深度最大的一个结点。

       0

       |

       1

     /   \

   2      3

比如说在这里,如果0为根的话,那么1是2和3的父亲结点,0是1的父亲结点,0和1都是2和3的公共祖先结点,但是1才是最近的公共祖先结点,或者说1是2和3的所有祖先结点中距离根结点最远的祖先结点。

在求解最近公共祖先为问题上,用到的是Tarjan的思想,从根结点开始形成一棵深搜树,非常好的处理技巧就是在回溯到结点u的时候,u的子树已经遍历,这时候才把u结点放入合并集合中,这样u结点和所有u的子树中的结点的最近公共祖先就是u了,u和还未遍历的所有u的兄弟结点及子树中的最近公共祖先就是u的父亲结点。以此类推。。这样我们在对树深度遍历的时候就很自然的将树中的结点分成若干的集合,两个集合中的所属不同集合的任意一对顶点的公共祖先都是相同的,也就是说这两个集合的最近公共最先只有一个。对于每个集合而言可以用并查集来优化,时间复杂度就大大降低了,为O(n + q),n为总结点数,q为询问结点对数。

另外Tarjan解法,是一个离线算法,就是说它必须将所有询问先记录下来,再一次性的求出每个点对的最近公共祖先,只有这样才可以达到降低时间复杂度。
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=40012;//node
const int maxm=252;//query
int res[maxm][4],n,m,t,query,head[maxn],Head[maxn],dis[maxn],vis[maxn],fa[maxn],NE;
// 记录结果,res[i][0]: u   res[i][1]: v  res[i][2]: lca(u, v)
struct node
{
    int u,v,d,next;
}Edge[maxn<<2],edge[maxn<<2];
void addEdge(int u,int v,int d)
{
    Edge[NE].u=u,Edge[NE].v=v,Edge[NE].d=d,Edge[NE].next=head[u];
    head[u]=NE++;
}
void addedge(int u,int v,int d)
{
    edge[NE].u=u,edge[NE].v=v,edge[NE].d=d,edge[NE].next=Head[u];
    Head[u]=NE++;
}
int find(int i)
{
    return fa[i]==i?i:find(fa[i]);
}
void Tarjan(int u){
    vis[u] = 1;
    fa[u] = u;
    for (int i = Head[u];i!=-1; i=edge[i].next){
        int v=edge[i].v;
        if(vis[v]){
            res[Edge[i].d][2]=find(v); // 存的是最近公共祖先结点
        }
    }
    for(int i= head[u];i!=-1;i=Edge[i].next){
        int v=Edge[i].v;
        if(!vis[v]){
            dis[v] = dis[u] + Edge[i].d;
            Tarjan(v);
            fa[v] = u;
        }
    }
}
int main()
{
    //freopen("//media/学习/ACM/input.txt","r",stdin);
    scanf("%d",&t);
    int i,j,u,v,d;
    while(t--)
    {
        memset(head,-1,sizeof(head));
        memset(Head,-1,sizeof(Head));
        memset(vis,0,sizeof(vis));
        scanf("%d%d",&n,&m);
        for(fa[n]=n,NE=0,i=1;i<n;i++)
        {
            fa[i]=i;
            scanf("%d%d%d",&u,&v,&d);
            addEdge(u,v,d);
            addEdge(v,u,d);
        }
        for(dis[1]=NE=0,i=1;i<=m;i++)
        {
            scanf("%d%d",&u,&v);
            addedge(u,v,i);
            addedge(v,u,i);
            res[i][0]=u,res[i][1]=v;
        }
        Tarjan(1);
        for(i=1;i<=m;i++)
        {
            printf("%d\n",dis[res[i][0]]+dis[res[i][1]]-2*dis[res[i][2]]);
        }
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值