LCA在线算法ST&&DFS:
推介博客:http://www.cnblogs.com/scau20110726/archive/2013/05/26/3100812.html
在线算法DFS+ST描述(思想是:将树看成一个无向图,u和v的公共祖先一定在u与v之间的最短路径上):
(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算法是在线算法,所以这个算法也是在线算法。
举例说明:
T=<V,E>,其中V={A,B,C,D,E,F,G},E={AB,AC,BD,BE,EF,EG},且A为树根。
则图T的DFS结果为:A->B->D->B->E->F->E->G->E->B->A->C->A,要求D和G的最近公共祖先, 则LCA[T ,D, G] = RMQ(L,R[D], R[G])= RMQ(L,3, 8),L中第4到7个元素的深度分别为:1,2,3,3,则深度最小的是B。
POJ1330
题意:
给出一些点的亲子关系,求两个点之间的最近公共祖先。
题解:
用LCA在线算法解决,求出这棵树的欧拉序列后,RMQ查询欧拉序列区间内的最小值。
代码:
#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std ;
#define MAX 10005
struct Node
{
int next , to ;
}edge[MAX*2];
//P:欧拉序列编号,depth:深度,Fir:点编号位置
int Fir[MAX] , P[MAX*2] , depth[2*MAX] ,head[MAX] , dp[MAX*2][16];
bool flag[MAX] ;
int tot ,tt;
void addEdge(int u , int v)
{
edge[tot].to = v ;
edge[tot].next = head[u] ;
head[u] = tot++ ;
}
void DFS(int u , int d)
{
flag[u] = true ;
P[++tt] = u ;
Fir[u] = tt ;
depth[tt] = d ;
for(int k = head[u] ; k != -1 ; k = edge[k].next)
{
if(!flag[edge[k].to])
{
DFS(edge[k].to , d+1) ;
P[++tt] = u ;
depth[tt] = d ;
}
}
}
void ST(int n)
{
for(int i=1;i<=n;i++) dp[i][0]=i;
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(i+(1<<(j-1))<=n)
{
int a=dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
dp[i][j]=(depth[a]<depth[b])?a:b;
}
else dp[i][j]=dp[i][j-1];
}
void query(int a,int b)
{
int k=0;
while((1<<(k+1))<=b-a+1) k++;
int ra=dp[a][k],rb=dp[b-(1<<k)+1][k];
if(depth[ra]>depth[rb]) printf("%d\n",P[rb] );
else printf("%d\n",P[ra] );
}
int main()
{
int T , N , u , v;
scanf("%d" , &T) ;
while(T --)
{
scanf("%d" , &N) ;
tot = tt = 0 ;
memset(head , -1 , sizeof(head)) ;
memset(flag , false , sizeof(flag)) ;
for(int i = 1 ; i < N ; i ++)
{
scanf("%d%d" , &u , &v) ;
addEdge(u , v) ;
addEdge(v , u) ;
flag[v] = true ;
}
int a , b ;
for(a = 1 ; a <= N ; a ++)
if(!flag[a]) break ;
memset(flag , false , sizeof(flag)) ;
DFS(a , 1) ;
ST(2*N-1) ;
scanf("%d%d" , &a , &b) ;
if(Fir[a] < Fir[b]) query(Fir[a] , Fir[b]) ;
else query(Fir[b] , Fir[a]) ;
}
return 0 ;
}
POJ1470
题意:
给出每个点的子节点都是哪些点,并输入一些查询区间,求在这些区间中,每个点都做了多少次最近公共祖先。
题解:
由于ST查询的时间复杂度为O(1),本题同样可以处理后在线查询,在一个ans数组中记录每个点做公共祖先的次数,不断更新即可。
代码:
#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std ;
#define MAX 1805
struct Node
{
int next , to ;
}edge[MAX*2];
//P:欧拉序列编号,depth:深度,Fir:点编号位置
int Fir[MAX] , P[MAX*2] , depth[2*MAX] ,head[MAX] , dp[MAX*2][16];
bool flag[MAX] ;
int ans[MAX] ,x[MAX*MAX] , y[MAX*MAX];
int tot ,tt;
void addEdge(int u , int v)
{
edge[tot].to = v ;
edge[tot].next = head[u] ;
head[u] = tot++ ;
}
void DFS(int u , int d)
{
flag[u] = true ;
P[++tt] = u ;
Fir[u] = tt ;
depth[tt] = d ;
for(int k = head[u] ; k != -1 ; k = edge[k].next)
{
if(!flag[edge[k].to])
{
DFS(edge[k].to , d+1) ;
P[++tt] = u ;
depth[tt] = d ;
}
}
}
void ST(int n)
{
for(int i=1;i<=n;i++) dp[i][0]=i;
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(i+(1<<(j-1))<=n)
{
int a=dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
dp[i][j]=(depth[a]<depth[b])?a:b;
}
else dp[i][j]=dp[i][j-1];
}
int query(int a,int b)
{
int k=0;
while((1<<(k+1))<=b-a+1) k++;
int ra=dp[a][k],rb=dp[b-(1<<k)+1][k];
if(depth[ra]>depth[rb]) return P[rb] ;
else return P[ra];
}
int main()
{
int N , u , v ,num;
char ch1[2] , ch2[2] ;
while(scanf("%d" , &N)!= EOF)
{
tot = tt = 0 ;
memset(head , -1 , sizeof(head)) ;
memset(flag , false , sizeof(flag)) ;
memset(ans , 0 , sizeof(ans)) ;
for(int i = 1 ; i <= N ; i ++)
{
scanf("%d:(%d)" , &u , &num) ;
//printf("%d %d\n" , u , num) ;
while(num --)
{
scanf("%d" , &v) ;
//printf("%d " , v) ;
addEdge(u , v) ;
addEdge(v , u) ;
flag[v] = true ;
}
}
int a , b ;
for(a = 1 ; a <= N ; a ++)
if(!flag[a]) break ;
memset(flag , false , sizeof(flag)) ;
DFS(a , 1) ;
ST(2*N-1) ;
int t , temp ;
scanf("%d" , &t) ;
getchar() ;
for(int i = 1 ; i <= t ; i ++)
{
scanf("%1s%d%d%1s",ch1,&x[i],&y[i],ch2);
//scanf(" (%d %d)" , a , b) ;
if(Fir[x[i]] < Fir[y[i]]) temp = query(Fir[x[i]] , Fir[y[i]]);
else temp = query(Fir[y[i]] , Fir[x[i]]) ;
ans[temp] ++;
}
for(int i = 1 ; i <= N ; i ++)
{
if(ans[i]) printf("%d:%d\n" , i , ans[i]) ;
}
}
return 0 ;
}