RMQ-区间最值问题

区间最值询问是求给定区间最值的问题。若总区间为[1,N],通常是有多次查询,每次查询是不同的总区间的子区间。

简单的方法是对每个子区间遍历从而找到最值,时间复杂度是O(N),但是如果有多次的查询,效率就会很低。而解决这个问题的一个很好的在线算法便是ST(Sparse_Table)算法

算法思想

预处理

ST算法在O(nlogn)的预处理以后可以实现O(1)的查询效率。也就是说我们把大量的区间的最值预先求出来,之后查询时就可以直接获取。那么重点首先就是预处理。

ST算法使用了动态规划的思想:先计算一些区间的最小值,然后把每个询问区间拆成若干个已经计算了最小值的区间,并统计这些区间的最小值的最小值,从而得到答案。
为了更有效更快的预处理,我们把每个预处理的区间长度定为2的非负整数次幂的,因此设定d[i][j]表示区间[i,i+2^j-1] 最小值(本文假设都是求最小值)。那么我们就能得到动规方程:

        d[i][j] = min(d[i][j-1],d[i+2^j][j-1])(j>=1)

d[i][j-1] 表示 d[i][j] 前一半区间的最小值,d[i+2^j][j-1] 表示后一半区间的最小值。比如要求区间[1,8]的最值,那可以先求[1,4]与[5,8]的最值,[1,4]再分解为[1,2],[3,4],[5,8]再分解为[5,6],[7,8]。转换为动规过程即是:

  • d[1,3]= min(d[1,2],d[5,2])
    • d[1,2] =min(d[1][1],d[3][1])
    • d[5,2] = min(d(5,1),d[7,1])
      • d[1][1] = min(d[1][0],d[2][0])
      • d[3][1] = min(d[3][0],d[4][0])
      • d[5][1] = min(d[5][0],d[6][0])
      • d[7][1] = min(d[7][0],d[8][0])

分解下来是这样子的,但是预处理的计算过程是从下向上一步步执行的。

j的遍历范围是[1,log(N)],而i的遍历范围[0,N+1-2^j]。所以得到预处理的时间复杂度是O(nlgn).

而区间的初始值(f[i][0] =i)都是已知的,整体的ST算法预处理步骤代码如下:

for (int i = 0; i < N; ++i) {
        f[i][0] = a[i];
}

for (int j = 1; (1 << j) <= N; ++j) {
        for (int i = 0; i + (1 << j) - 1 < N; ++i) {
            f[i][j] = min(f[i][j-1], f[i+(1 << (j-1))][j-1]);
        }
}

求区间最值

预处理之后,每次查询都快速求解出答案。
若查询[a,b]之间的最小值,我们可以通过求 min(f[a,k],f[b-2^k+1,k]) 得出,其中K是满足 2^k<=b-a+1的最大值, 这样能够满足f[a,k]和f[b-2^k+1,k] 对应的区间覆盖整个[a,b]. 因此
k= trunc(log_2(b-a+1)) = trunc(ln(b-a+1)/ln(2)). 求值代码较简单,此处就不贴了。

总结

ST巧妙的利用了二进制以及动态规划思想,区间最值的种种特性,降低了代码复杂度,其很好应用例子是求两个节点的最小公共父节点。

你可能感兴趣的:(算法)