1. 程式人生 > >hdu 2586 How far away?(LCA模板題+離線tarjan演算法)

hdu 2586 How far away?(LCA模板題+離線tarjan演算法)

How far away ?

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


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 1 2 100 1 2 2 1  

 

Sample Output 10 25 100 100     題目大意: 給你一棵樹,求任兩點的距離。   這是LCA模板題。 求出兩點的公共祖先ancst,然後dis[i,j]=depth[i]+depth[j]-depth[ancst]*2;其中depth是節點在有根樹中的深度。 離線tarjan演算法。。ctrlc ctrlv一段大佬的解釋吧。(來自zhouzhendong的cnblogs)  

LCA_Tarjan

  TarjanTarjan 演算法求 LCA 的時間複雜度為 O(n+q)O(n+q) ,是一種離線演算法,要用到並查集。(注:這裡的複雜度其實應該不是 O(n+q)O(n+q) ,還需要考慮並查集操作的複雜度 ,但是由於在多數情況下,路徑壓縮並查集的單次操作複雜度可以看做 O(1)O(1),所以寫成了 O(n+q)O(n+q) 。)

  TarjanTarjan 演算法基於 dfs ,在 dfs 的過程中,對於每個節點位置的詢問做出相應的回答。

  dfs 的過程中,當一棵子樹被搜尋完成之後,就把他和他的父親合併成同一集合;在搜尋當前子樹節點的詢問時,如果該詢問的另一個節點已經被訪問過,那麼該編號的詢問是被標記了的,於是直接輸出當前狀態下,另一個節點所在的並查集的祖先;如果另一個節點還沒有被訪問過,那麼就做下標記,繼續 dfs 。

  當然,暫時還沒那麼容易弄懂,所以建議結合下面的例子和標算來看看。

(下面的集合合併都用並查集實現)

  比如:8114138−1−14−13 ,此時已經完成了對子樹 11 的子樹 1414 的 dfsdfs 與合併( 1414 子樹的集合與 11 所代表的集合合併),如果存在詢問 (13,14)(13,14) ,則其 LCA 即 getfather(14)getfather(14) ,即 11 ;如果還存在由節點 1313 與 已經完成搜尋的子樹中的 節點的詢問,那麼處理完。然後合併子樹 1313 的集合與其父親 11 當前的集合,回溯到子樹 11 ,並深搜完所有 11 的其他未被搜尋過的兒子,並完成子樹 11 中所有節點的合併,再往上回溯,對節點 11 進行類似的操作即可。

  大意就是,後根dfs一棵樹,如果子樹遍歷完了,就把它整個加入子樹根節點的父親節點的並查集中。 遇到詢問,就判斷它的另一點是否已經遍歷過了,如果是,就getfather。還是自己畫畫圖,應該不難懂吧。   第一次寫,比較粗糙,附AC程式碼:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <queue>
#include <deque>
#include <stack>
#include <map>
#include <set>
typedef long long LL;
const int MAX_N=40000;
const int MAX_M=200;
const int INF=1000000000;

struct tedge
{
    int to,w,next;
};
tedge edge[MAX_N*2+5];
int head1[MAX_N+5],cnt1;

void addedge(int a,int b,int c)
{
    edge[cnt1]=(tedge){b,c,head1[a]};head1[a]=cnt1++;
    edge[cnt1]=(tedge){a,c,head1[b]};head1[b]=cnt1++;
}

struct tquery
{
    int to,next;
    int index;
};
tquery query[MAX_M*2+5];
int head2[MAX_N+5],cnt2;

void addquery(int a,int b,int i)
{
    query[cnt2]=(tquery){b,head2[a],i};head2[a]=cnt2++;
    query[cnt2]=(tquery){a,head2[b],i};head2[b]=cnt2++;
}

int fa[MAX_N];

int getf(int x)
{
    if(fa[x]==x)
        return x;
    else
        return fa[x]=getf(fa[x]);
}

int vis[MAX_N+5];
int depth[MAX_N+5];
int ans[MAX_M+5];

void LCA(int x,int pa,int dis)
{
    for(int i=head1[x];i!=-1;i=edge[i].next)
    {
        int l=edge[i].to;
        if(l!=pa)
        {
            LCA(l,x,dis+edge[i].w);
        }
    }
    depth[x]=dis;
    for(int i=head2[x];i!=-1;i=query[i].next)
    {
        int l=query[i].to;
        if(vis[l])
        {
            int ancst=getf(l);
            ans[query[i].index]=depth[l]+depth[x]-depth[ancst]*2;
            //printf("%d %d %d\n",l,x,ancst);
        }
    }
    fa[x]=pa;vis[x]=1;
}

void init()
{
    memset(head1,-1,sizeof(head1));cnt1=0;
    memset(head2,-1,sizeof(head2));cnt2=0;
    memset(vis,0,sizeof(vis));
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        init();
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1,a,b,c;i<=n-1;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            addedge(a,b,c);
        }
        for(int i=1,a,b;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            addquery(a,b,i);
        }
        for(int i=1;i<=n;i++)
            fa[i]=i;
        LCA(1,-1,0);
        for(int i=1;i<=m;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}
View Code