轉自:鼻子很帥的豬
問題描述
對於該問題,最容易想到的算法是分別從節點u和v回溯到根節點,獲取u和v到根節點的路徑P1,P2,其中P1和P2可以看成兩條單鏈表,這就轉換成常見的一道面試題:【判斷兩個單鏈表是否相交,如果相交,給出相交的第一個點。】。該算法總的複雜度是O(n)(其中n是樹節點個數)。
本文介紹了兩種比較高效的算法解決這個問題
在線算法:用比較長的時間做預處理,但是等信息充足以後每次回答詢問只需要用比較少的時間。離線算法:先把所有的詢問讀入,然後一起把所有詢問回答完成。 (Tarjan算法 )
(1)DFS:從樹T的根開始,進行深度優先遍歷(將樹T看成一個無向圖),並記錄下每次到達的頂點。第一個的結點是root(T),每經過一條邊都記錄它的端點(歐拉環遊)。由於每條邊恰好經過2次,因此一共記錄了2n+1個結點,用E[1, ... , 2n+1]來表示。比如
(2)計算R:用R[i]表示E數組中第一個值爲i的元素下標,即如果R[u] < R[v]時,DFS訪問的順序是E[R[u], R[u]+1, …, R[v]]。雖然其中包含u的後代,但深度最小的還是u與v的公共祖先。
(3)RMQ:當R[u] ≥ R[v]時,LCA[T, u, v] = RMQ(L, R[v], R[u]);否則LCA[T, u, v] = RMQ(L, R[u], R[v]),計算RMQ。
由於RMQ中使用的ST算法是在線算法,所以這個算法也是在線算法。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
#define N 902
int n, m;
vector<int> map[N];
int E[N<<1], L[N<<1], R[N], flag[N], Min[N<<1][12], cnt, rt;
void dfs( int x, int h ){ //參數h是用來表示節點所在的層數的,在dfs的時候直接就給L[]數組賦值,巧妙。
E[++cnt]= x; L[cnt]= h;
if( !flag[x] ){ R[x]= cnt; flag[x]= 1; }//在數組R[]中記錄第一次出現的位置
for( size_t i= 0; i< map[x].size(); ++i ){
int v= map[x][i];
if( flag[v]== 0 ) dfs( v, h+ 1 );
E[++cnt]= x; L[cnt]= h; //注意此處啊,不是一般的深度優先遍歷,這可是歐拉環遊路徑。
}
}
void init(){
for( int i= 1; i<= cnt; ++i ) Min[i][0]= i;
for( int i= 1; 1<<i< cnt; i++ )
for( int j= 0, s= 1<< (i-1); j+ s< cnt; j++ ){
if( L[ Min[j][i-1] ]< L[ Min[j+ s][i-1] ] )Min[j][i]= Min[j][i-1];
else Min[j][i]= Min[j+s][i-1];
}
}
int rmq( int x, int y ){
if( x> y ) x^= y^= x^= y;
int d= y- x+ 1, t= 0;
while( 1<<t <= d ) t++; t--;
if( L[ Min[x][t] ]< L[ Min[y-(1<<t)+1][t] ] ) return Min[x][t];
else return Min[y-(1<<t)+1][t];
}
int main(){
while( scanf("%d",&n)!= EOF ){
for( int i= 0; i<= n; ++i ) flag[i]= 0;
cnt= 0;
for( int i= 1; i<= n; ++i ){
int u, num, v;
scanf("%d",&u);
while( getchar()!= '(');
scanf("%d",&num);
while( getchar()!= ')');
while( num-- ){
scanf("%d",&v );
map[u].push_back(v); flag[v]++;
}
}
for( int i= 1; i<= n; ++i )
if( flag[i]== 0 ){ rt= i; break; }
for( int i= 0; i<= n; ++i ) flag[i]= 0;
dfs( rt, 0 );
init();
for( int i= 0; i<= n; ++i ) flag[i]= 0;
scanf("%d",&m );
for( int i= 1; i<= m; ++i ){
while( getchar()!= '(' );
int u, v;
scanf("%d%d", &u, &v );
int pos= rmq( R[u], R[v] );
flag[ E[pos] ]++;
while( getchar()!= ')' );
}
for( int i= 1; i<= n; ++i )
if( flag[i] ) printf("%d:%d\n", i, flag[i] );
for( int i= 0; i<= n; ++i ) map[i].clear();
}
return 0;
}
LCA(u){
MAKE_SET(u)
ancestor[FIND(u)]= u
for( each child v of u ){
LCA(v)
UNION(u,v)
ancestor[FIND(v)]=u
}
flag[u]= 1;
for( each node v such that [u,v] in P )
if( flag[v] )
print "The least common ancestor of 'u' and 'v' is " ancestor[ FIND(v) ]
}
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define N 10010
vector<int> map[N];
int n, uset[N], ancestor[N], u, v, flag[N], deg[N], root;
//並查集的操作。
int find( int x ){if(uset[x]==x)
return x; else return uset[x]=Find(uset[x]);
}
void Union( int x, int y ){
int a= find(x), b= find(y);
uset[a]= b; }
void LCA( int x ){
uset[x]= x; ancestor[x]= x;
for( size_t i= 0; i< map[x].size(); ++i ){
int y= map[x][i];
LCA( y );
Union( x, y );
ancestor[ find(y) ]= x;
}
flag[x]= 1;
if( x== u && flag[v] ){
printf("%d\n", ancestor[ find(v) ] );
return; }
else if( x== v && flag[u] ){
printf("%d\n", ancestor[ find(u) ] );
return; }
}
int main(){
int test;
scanf("%d",&test );
while( test-- ){
scanf("%d",&n);
for( int i= 0; i<= n; ++i ) { ancestor[i]= 0; flag[i]= 0; deg[i]= 0; }
for( int i= 1; i< n; ++i ){
int x, y;
scanf("%d%d", &x, &y);
map[x].push_back(y);
deg[y]++;
}
scanf("%d%d",&u,&v);
for( int i= 1; i<= n; ++i )
if( deg[i]== 0 ) root= i;
LCA( root );
for( int i= 0; i<= n; ++i ) map[i].clear();
}
return 0;
}