zoukankan      html  css  js  c++  java
  • HDU 2586 How far away? Tarjan算法 并查集 LCA

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
    Total Submission(s): 23506    Accepted Submission(s): 9329

    Problem Description

    There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this "How far is it if I want to go from house A to house B"? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path("simple" means you can't visit a place twice) between every two houses. Yout task is to answer all these curious people.
     

     

    Input

    First line is a single integer T(T<=10), indicating the number of test cases.
      For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0<k<=40000).The houses are labeled from 1 to n.
      Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.
     


    Output

    For each test case,output m lines. Each line represents the answer of the query. Output a bland line after each test case.
     


    Sample Input

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

    Sample Output

    10
    25
    100
    100
     
     
    本題大意:
      給出一棵樹,和樹邊長度,給出一系列詢問點,求兩點間的距離。
     
    解題思路:
      本題可用Tarjan算法求出根節點到各點的最短距離,然後結合并查集來先求出最近公共祖先(LCA),這樣兩個點之間的距離就可以表示為:根節點到點1的距離 + 根節點到點2的距離 - 兩倍的根節點到LCA的距離。
      思想核心是求LCA,所以本文記錄一個用Tarjan結合并查集求LCA的方法。
      實現方法:選定一個點為根節點,對樹進行DFS搜索,搜索回溯時把子節點的并查集信息連到父節點上去。在搜索每個點的時候,都檢查是否是詢問點,如果是詢問點,則檢查與之對應的詢問點是否被訪問過,如果訪問過,那麼它的并查集父親必然到知道他們的公共祖先上去,為什麼?這是我參考其他大神代碼的時候思考一段時間才想出來為什麼的。根據DFS搜索的特性,搜索回溯時都把下級的并查集信息往上連,而DFS是深度優先,則回溯距離必然盡可能小,即如果回溯一級就找到另一條路的話,就會繼續往下搜,而如果這兩條路上各存著詢問點對的其中一個,先被搜索到的那個節點的并查集信息只往上并了一級,那就是最近的公共祖先。在處理尋問點對的時候,如果找到這樣的并查集被修改過的(即被訪問過的)的對應點,那就把它們兩個的LCA記下即可。
      把整棵樹遍歷完的時候,所有詢問點對的LCA也都求出來了,最後按照尋問順序,按照公式計算并輸出距離即可。
     
    結合一下AC代碼理解:
    #include<bits/stdc++.h>
    using namespace std;
    #define N 40000
    
    typedef struct Edge
    {
        int t,v;
        int next;
    }edge;
    
    edge E[N*2],e[N*2];
    int cnt;
    int head1[N],head2[N];//head1用于樹上節點的串連,head2用於詢問節點的串連
    int dis[N],f[N],vis[N];//dis各點到根節點的距離,f為并查集
    int ans[N][3];          //ans[0]和[1]記錄詢問點對,ans[3]記錄他們的公共祖先(LCA)
    int n,m;
    void addedge(int u,int v,int d,edge *a,int *head)
    {
        a[cnt].t=v;
        a[cnt].v=d;
        a[cnt].next=head[u];
        head[u]=cnt++;
    
        a[cnt].t=u;
        a[cnt].v=d;
        a[cnt].next=head[v];
        head[v]=cnt++;
    }
    
    int findx(int a)//并查集
    {
        if(a!=f[a])
            return f[a]=findx(f[a]);
        return a;
    }
    
    void getLCA(int p)//Tarjan算法求LCA
    {
        vis[p]=1;
        f[p]=p;        //進入時初始化并查集
    
        for(int i=head2[p];i!=-1;i=e[i].next)//檢查詢問點對,是否發現已訪問
        {
            if(vis[e[i].t])
                ans[e[i].v][2]=findx(e[i].t);
        }
    
        for(int i=head1[p];i!=-1;i=E[i].next)//DFS搜索
        {
            if(!vis[E[i].t])
            {
                dis[E[i].t]=dis[p]+E[i].v;  //更新距離
                getLCA(E[i].t);
                f[E[i].t]=p;                //搜索過后更新子節點的并查集
            }
        }
    }
    
    int main()
    {
        //freopen("a.txt","r",stdin);
        int T;
        scanf("%d",&T);
        while(T--)
        {
            cnt=0;
            memset(head1,-1,sizeof(head1));
            memset(head2,-1,sizeof(head2));
            scanf("%d%d",&n,&m);
            for(int i=1;i<=n-1;i++)
            {
                int a,b,v;
                scanf("%d%d%d",&a,&b,&v);
                addedge(a,b,v,E,head1);
            }
            cnt=0;
            for(int i=1;i<=m;i++)       //先全部記錄后離線處理
            {
    
                scanf("%d%d",&ans[i][0],&ans[i][1]);
                addedge(ans[i][0],ans[i][1],i,e,head2);
            }
    
            memset(vis,0,sizeof(vis));
            dis[1]=0;
            getLCA(1);
    
            for(int i=1;i<=m;i++)
                printf("%d
    ",dis[ans[i][1]]+dis[ans[i][0]]-2*dis[ans[i][2]]);
        }       //最短距離 = 節點1的距離 + 節點2的距離 - 2*LCA的最短距離
    }           //(這裡最短距離是指到根節點的距離)
  • 相关阅读:
    [LeetCode] Excel Sheet Column Title
    [LeetCode] Paint House
    [LeetCode] Interleaving String
    [LeetCode] Plus One
    [LeetCode] Spiral Matrix
    [LeetCode] Spiral Matrix II
    [LeetCode] Rotate Image
    [LeetCode] Maximum Gap
    android学习日记13--数据存储之ContentProvide
    android学习日记0--开发需要掌握的技能
  • 原文地址:https://www.cnblogs.com/Lin88/p/9510766.html
Copyright © 2011-2022 走看看