算法之LCA和RMQ

来源:互联网 发布:南风天刀脸型数据 编辑:程序博客网 时间:2024/06/06 05:42

最近公共祖先(Least Common Ancestors)

对于有根树T的两个结点uv,最近公共祖先LCA(T,u,v)表示一个结点x,满足xuv的祖先且x的深度尽可能大。另一种理解方式是把T理解为一个无向无环图,而LCA(T,u,v)uv的最短路上深度最小的点。

1.ST算法(在线算法):

RMQ(Range Minimum/Maximum Query)问题是求区间最值问题。你当然可以写个O(n),但是万一要询问最值1000000遍,估计你就要挂了。这时候你可以放心地写一个线段树(前提是不写错)Ologn)的复杂度应该不会挂。但是,ST算法,它可以做到O(nlogn)的预处理,O(1)地回答每个询问。来看一下ST算法是怎么实现的(以最大值为例):      

       首先是预处理,用一个DP解决。设a[i]是要求区间最值的数列,f[i, j]表示从第i个数起连续2^j个数中的最大值

例如数列3 2 4 5 6 8 1 2 9 7 ,f[10]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。f[12]=5f[13]=8f[20]=2f[21]=4……从这里可以看出f[i,0]其实就等于a[i]。这样,Dp的状态、初值都已经有了,剩下的就是状态转移方程。我们把f[ij]平均分成两段(因为f[ij]一定是偶数个数字),从ii+2^(j-1)-1为一段,i+2^(j-1)i+2^j-1为一段(长度都为2^j-1)。用上例说明,当i=1j=3时就是3,2,4,5  6,8,1,2这两段。f[ij]就是这两段的最大值中的最大值。于是我们得到了动规方程

F[i, j]=maxF[ij-1], F[i + 2^(j-1)j-1]
     
接下来是得出最值,也许你想不到计算出f[ij]有什么用处,一般毛想想计算max还是要O(logn),甚至O(n)。但有一个很好的办法,做到了O1)。还是分开来。如在上例中我们要求区间[28]的最大值,就要把它分成[2,5][5,8]两个区间,因为这两个区间的最大值我们可以直接由f[22]f[52]得到。扩展到一般情况,就是把区间[LR]分成两个长度为2^n的区间(保证有f[ij]对应)。直接给出表达式:
k := ln(R-L+1) / ln(2);
ans := max(F[L
k], F[R - 2^k+1, k]);

这样就计算了从i开始,长度为2^t次的区间和从r-2^i+1开始长度为2^t的区间的最大值(表达式比较烦琐,细节问题如加11需要仔细考虑.

 1 //初始化
 2 INIT_RMQ
 3 //max[i][j]中存的是重j开始的i个数据中的最大值,最小值类似,num中存有数组的值
 4     for i : 1 to n
 5         max[0][i] = num[i]
 6     for i : 1 to log(n)/log(2)
 7         for j : 1 to n
 8             max[i][j] = MAX(max[i-1][j], max[i-1][j+2^(i-1)]
 9 //查询
10 RMQ(i, j)
11     k = log(j-i+1/ log(2)
12     return MAX(max[k][i], max[k][j-2^k+1])

下面给出LCA问题向RMQ问题的转化方法。

对树进行深度优先遍历,每当进入或回溯到某个结点时,将这个结点的深度存入数组
E
最后一位。同时记录结点i在数组中第一次出现的位置(事实上就是进入结点i时记录的
位置),记做R[i]。如果结点E[i]的深度记做D[i],易见,这时求LCA(T,u,v),就等价于求
E[RMQ(D,R[u],R[v])]
(R[u]<R[v])。例如,对于第一节的例一,求解步骤如下:
数列E[i]为:1,2,1,3,4,3,5,3,1

R[i]为:1,2,4,5,7
D[i]
为:0,1,0,1,2,1,2,1,0

于是有:

LCA(T,5,2) = E[RMQ(D,R[2],R[5])] = E[RMQ(D,2,7)] = E[3] = 1
LCA(T,3,4) = E[RMQ(D,R[3],R[4])] = E[RMQ(D,4,5)] = E[4] = 3
LCA(T,4,5) = E[RMQ(D,R[4],R[5])] = E[RMQ(D,5,7)] = E[6] = 3

易知,转化后得到的数列长度为树的结点数的两倍减一,所以转化后的RMQ问题与LCA
问题的规模同次。

2.Tarjan算法

Tarjan算法是一个离线算法,也就是说只有先获得所有的查询,再按一个特定的顺序进行运算。

LCA(u)   
{   
     Make-Set(u)   
     ancestor[Find-Set(u)]=u   
     对于u的每一个孩子v   
     {   
         LCA(v)   
         Union(u)   
         ancestor[Find-Set(u)]=u   
     }   
     checked[u]=true  
     对于每个(u,v)属于P   
     {   
         if checked[v]=true  
        then {   
             回答u和v的最近公共祖先为 ancestor[Find-Set(v)]   
         }   
     }   
}