LCA在线算法ST&&DFS->POJ1330&&POJ1470

本文介绍了一种LCA在线算法ST&DFS,通过深度优先搜索建立欧拉序列,利用RMQ查询找到两个节点间的最近公共祖先。提供两道POJ题目解析及完整代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值