RMQ作为学习Suffix Array的基本知识之一,我想在写SA总结之前写下这一部分的知识。
RMQ问题可以算是经典问题中的经典,目的就是希望用较短的时间完成求区间最小值的操作。
方法有很多种,最简单的直接线性统计的效率极其低下,也比较白痴,差不多都应该会。
下面讲下几个比较好的RMQ问题的算法。
方法一:线段树
线段树作为维护区间性质的最佳利器,绝对是不错的选择。写起来可以递归,非常的方便。
但是他也是有缺点的,最大的问题就是虽然说是O(nlogn)-O(logn)的时间复杂度中的常数比较大。从实际运行效率上看不是很好。
我前面的日志里面写过一些线段树的题,线段树的用法大同小异,也比较好学,这里不再赘述。
方法二:RMQ的ST算法
ST算法是一个很不错的算法,复杂度为O(nlogn)-O(1)。这种算法运用的主要思想是倍增思想。
倍增思想的核心就是,通过递推使得结论成倍地提高。
这里我们定义这样一个数组f[i,j]表示从i开始向后数2j个元素的RMQ
递推方程就是
f[i,j]:=min(f[i,j-1],f[i+1 shl (j-1),j-1]);
边界:f[i,0]:=a[i];
这样我们就可以用这样的式子来解决RMQ问题:
设k:=trunc(ln(j-i+1)/ln(2))
RMQ(i,j):=min(f[i,k],f[j-(1 shl k)+1,k]);
对于某一些特殊的RMQ问题还有O(n)-O(1)的算法,方法在于分成长度特殊的段,然后再用这些段RMQ。这是将LCA问题转化为RMQ问题的途径。
方法三:笛卡尔树
这个方法的精髓在于将RMQ问题转化为LCA问题。
笛卡尔树的定义是,对于一个数组A来说,笛卡尔树的根的编号是这个数组的最小值的所在的编号k。他的左子树就是在数组A[1..k-1]上的一棵笛卡尔树,右子树是一棵在数组A[k+1..n]的一棵笛卡尔树。
这样RMQ(A,i,j)就被转化成了LCA(T,i,j)。
因为LCA问题离线Tarjan算法的复杂度是O(n)的,所以这个问题最终的复杂度在O(n)。
我的理解就是理论性比较强,并没有什么实际意义。
缺点是不支持在线提问。
以上就是RMQ问题的几种经典方法,如果想要透彻的理解好线段树和倍增法,还是需要一些题目的磨练。
推荐的一个题是MIPT上的105号MRQ Problem。数据真的是很强,有很多地方要注意,用ST时候要格外注意内存。
另外用P的同学,请使用Kylix编译器提交,否则这辈子估计过不了了啊……
线段树的代码以前贴过,这里给下我的ST算法的代码: