hdu2586 樹上兩點之間的距離 tarjan
阿新 • • 發佈:2019-02-04
How far away ?
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 6650 Accepted Submission(s): 2475
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 此題是樹上兩點之間的距離,直觀想法是暴力列舉樹根,dfs出所有距離,n^2的複雜度無法容忍。
那麼我們是否可以固定一個樹根,然後計算每個點到樹根的距離dist,假設a,b的LCA是c,那麼a到b這條路徑我們可以拆成a->c,c->b,這個資訊我們顯然無法得到,那麼我們加上兩端c->root,root->c,合併後就是a->root,root->b,也就是dist[a]+dist[b],因為我們多加了兩段2*dist[c],因此最後就是dist[a]+dist[b]-2*dist[c]。
這裡可用線上RMQ演算法來處理尤拉序列或者使用離線tarjan演算法。
線上RMQ演算法較好理解,而tarjan演算法有點難理解。
其實tarjan思想的本質就是遞迴處理,假設我們當前在處理u的點,我們須先處理u的所有子樹(這裡是遞迴),然後訪問完u的子樹,表示u已訪問完畢,我們將vis[u]置1,然後立刻處理與u向關聯的詢問,假設詢問(u,v),那麼我們考察一下v是否被訪問,假設vis[v]=0,那麼我們不處理,因為下次當我們訪問到v時再來處理(v,u){此時u已訪問},為了保證演算法正確性,我們對每個詢問標記兩次,(u,v)和(v,u),即保證處理到一次。而對於vis[v]=1的情況,我們要處理(u,v)的LCA,那麼LCA是什麼呢?畫張圖就知道了,一定是訪問完v往上走之後下到u,因此我們需要並查集記錄一下,對u每訪問完一個子樹,將子樹與u合併,然後讓該集合指向u,也就是上走到u,可以畫張圖,應該就能理解。
程式碼:
#include<iostream> #include<cstdio> #include<cstring> #define Maxn 40010 #define Maxm 210 using namespace std; struct edge{ int fr,to,w,lca,next; }p[Maxn<<1],ask[Maxm<<1]; int head[Maxn],ah[Maxn]; int tot,tot1; void addedge(int a,int b,int c){ p[tot].to=b; p[tot].w=c; p[tot].next=head[a]; head[a]=tot++; } void addedge1(int a,int b){ ask[tot1].fr=a; ask[tot1].to=b; ask[tot1].next=ah[a]; ah[a]=tot1++; } int fa[Maxn]; int findset(int x){ return fa[x]==x?x:(fa[x]=findset(fa[x])); } void unionset(int a,int b){ fa[findset(a)]=findset(b); } int vis[Maxn]; int anc[Maxn]; int dist[Maxn]; void LCA(int u,int fa){ for(int i=head[u];i!=-1;i=p[i].next){ int v=p[i].to; if(fa!=v){ dist[v]=dist[u]+p[i].w; LCA(v,u); unionset(u,v); //將子樹合併到父親 anc[findset(u)]=u; //維護新集合指向父親 } } vis[u]=1; //設定已訪問 for(int i=ah[u];i!=-1;i=ask[i].next){ //處理與u關聯的邊 int v=ask[i].to; if(vis[v]) //若v已訪問,則說明u,v的lca是v所在集合的指向 ask[i].lca=ask[i^1].lca=anc[findset(v)]; } } void init(int n){ tot=tot1=0; memset(head,-1,sizeof head); memset(ah,-1,sizeof ah); memset(vis,0,sizeof vis); for(int i=1;i<=n;i++) fa[i]=i; } int main() { int t,n,m,a,b,c; cin>>t; while(t--){ cin>>n>>m; init(n); for(int i=1;i<n;i++){ scanf("%d%d%d",&a,&b,&c); addedge(a,b,c); addedge(b,a,c); } for(int i=1;i<=m;i++){ scanf("%d%d",&a,&b); addedge1(a,b); addedge1(b,a); } dist[1]=0; LCA(1,-1); for(int i=0;i<tot1;i+=2) printf("%d\n",dist[ask[i].fr]+dist[ask[i].to]-2*dist[ask[i].lca]); } return 0; }