转自:鼻子很帅的猪
问题描述
对于该问题,最容易想到的算法是分别从节点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;
}