首页 > 代码库 > RMQ算法
RMQ算法
上午在做一个题,结果怎么也做不出来,然后看了篇博客,发现其在求最小的字典序的时候用到了RMQ算法。
问徐大佬这是个什么东西,大佬说:你们肯定学过,这个东西1个月以前60级的就问过我了。
为了不让学弟学妹落下,我决定还是学学RMQ算法吧。。
one 前言
RMQ(Range Minimum/Maximum Query),是指区间查询最值的算法,我们对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n)是求在序列A中,在区间i,j中出现的最小/最大值。
two RMQ算法
对于该类型的问题我们首先想到的就是遍历,时间复杂度为o(n),但当我们要查询一组较大的数据时,我们就无法用该算法在有效的时间内进行查询。so,我们应该找一个简单快速的方法来处理这类问题。该问题我们可以用一种比较高效的在线操作(ST算法)来解决这个问题。所谓在线算法,是指每个用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。ST(Sparse Table)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。
1.预处理
在预处理的时候我们一般用动态规划(dp)来解决。
设我们要求出数组A[i]的区间最值,f[i][j]数组是用来存放区间i~2^j的最值。
例如:
A数列为:3 2 4 5 6 8 1 2 9 7
f[1][0]表示从第一个数到第2^0个数的最大值 其实也就是3这个值。同理f[1][1]这个值就等于max(3,2)=3, f[1][2]=max(3,4,5,6)=6,f[1][3]=max(3,2,4,5,6,8,1,2)=8.
通过观察我们发现f[i][0]表示的就是从第i个点到第i个点的最值,显然就是他本身A[i]吧。(这样我们就可以得知dp的初始值)
这样dp的初始值,dp的状态我们都已经有了,剩下的就是状态转移方程了。然而这个状态转移方程这样才能得出呢??
我们把我们所要求的区间i~2^j划分成两个区间(两段,因为f[i][j]一定是个偶数个数字),我们把它划分成i~2^(j-1)-1与2^(j-1)~2^j这两段区间(这样我们就把这两段区间都划分成长度为2^(j-1))。
用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。F[i,j]就是这两段各自最大值中的最大值。于是我们得到了状态转移方程F[i, j]=max(F[i,j-1], F[i + 2^(j-1),j-1])
我们接下来来看看这种做法的代码实现
void RMQ(int x)//我们在这里对 x长度的一个数组进行区间查询最值 { for(int j=1;j<20;j++) for(int i=1;i<=x;i++) { maxn[i][j]=max(maxn[i][j-1],maxn[i+(1<<(j-1))][j-1]); minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1)]); }}
通过观察上述代码我们可以发现一个问题
在进行循环的时候外层循环的是j,内层循环的是i,这是为什么呢?我们是否可以把它换过来呢??
答案显然是不可以。
这就需要我们必须理解该动态转移方程的意义。
动态转移方程的意义是:我们对于一个序列,我们要先更新f[1][0],再更新f[2][0]这样我们才能更新出f[1][1]的值,也就是说我们要先更新出所有f[i][0]的值,然后再两个两个的进行更新,更新出一两个元素为长度的两个元素的最值,再进行更新,获得所有长度为4的最值。。。。
而如果我们把i放在外面,这样我们进行循环的时候,我们依次求出的是f[1][0],f[1][1],f[1][2],f[1][3];表示从元素1一直更新到元素2^3,这里的f[1][3]=max(max(a[1],a[2],a[3]),max(a[4],a[5],a[6],a[7])的值,然而,在这里我们并没有max(a[1],a[2],a[3]),max(a[4],a[5],a[6],a[7]))的值,这样我们就无法得到f[1][3]的值,这样显然这种方法是错误的。
2.询问
假如我们需要查询的区间为(i,j),那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂(可以重复,比如查询5,6,7,8,9,我们可以查询5678和6789)。
因为这个区间的长度为j - i + 1,所以我们可以取k=log2( j - i + 1),则有:RMQ(A, i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}。
举例说明,要求区间[2,8]的最大值,k = log2(8 - 2 + 1)= 2,即求max(F[2, 2],F[8 - 2 ^ 2 + 1, 2]) = max(F[2, 2],F[5, 2]);
在这里我们也需要注意一个地方,就是<<运算符和+-运算符的优先级。
比如这个表达式:5 - 1 << 2是多少?
答案是:4 * 2 * 2 = 16。所以我们要写成5 - (1 << 2)才是5-1 * 2 * 2 = 1。
RMQ算法