转载

求解区间最值 - RMQ - ST 算法介绍

解析

ST 算法是 RMQ(Range Minimum/Maximum Query)中一个很经典的算法,它天生用来求得一个区间的最值,但却不能维护最值,也就是说, 过程中不能改变区间中的某个元素的值 。O(nlogn) 的预处理和 O(1) 的查询对于需要大量询问的场景是非常适用的。接下来我们就来详细了解下 ST 算法的处理过程。

比如有如下长度为 10 的数组:

我们要查询 [1, 7] 之间的最大值,如果采用朴素的线性查找,复杂度O(n),而 ST 算法却只需要 O(1)的时间复杂度,因为 ST 算法预处理了一个 dp 数组。

我们用 dp[i][j] 表示从 i 开始的 2^j 个数的最值,表示 dp[i][j] “管辖” index=i 开始的 2^j 个数字,那么很显然,任何一段区间都能被两个 dp 元素管辖到。比如上面说的 [1, 7],就能被dp[1][2] 和 dp[4][2]管辖到,而 max(dp[1][2], dp[4][2])也就是[1, 7] 的最值了。

如何得出是 dp[1][2] 和 dp[4][2] 这两个元素?很简单,让dp[1][n](2^n <= 区间个数)中的n尽可能大就得到了第一个元素,从而可以推得第二个元素,两个元素的管辖范围大小是一样的。

这样我们只需预处理一个 dp 数组就可以了,而这个预处理是一个动态规划的过程,转移方程为:

dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);

而 dp 数组的预处理和 RMQ 的求解过程正好是个逆过程。

实战

POJ 上有一道 ST 算法的模板题 Balanced Lineup ,只需预处理两个数组即可,一个表示最大值,另一个表示最小值。

完整代码:

#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; #define N 50005 int maxn[N][32], minn[N][32];  int a[N];  void ST(int n) {   for (int i = 1; i <= n; i++)     maxn[i][0] = minn[i][0] = a[i];    int k = log(n * 1.0) / log(2.0);    for (int j = 1; j <= k; j++)     for (int i = 1; i <= n; i++) {     if (i + (1 << j) - 1 > n) break;       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]);     } }  int getAns(int x, int y) {   int k = log(y - x + 1.0) / log(2.0);   return max(maxn[x][k], maxn[y + 1 - (1 << k)][k]) - min(minn[x][k], minn[y + 1 - (1 << k)][k]); }  int main() {   int n, cas;   scanf("%d%d", &n, &cas);   for (int i = 1; i <= n; i++)     scanf("%d", &a[i]);    ST(n);    while (cas--) {     int x, y;     scanf("%d%d", &x, &y);     printf("%d/n", getAns(x, y));   }    return 0; }

Javascript模板:

var G = {   dp: [], // dp[i][j] 表示从 index=i 开始的连续 2^j 个元素中的最值    init: function(a) {     var n = a.length;      for (var i = 0; i < n; i++)       G.dp[i] = [], G.dp[i][0] = a[i];       var k = ~~(Math.log(n) / Math.log(2));      for (var j = 1; j <= k; j++)       for (var i = 0; i < n; i++) {         if (i + (1 << j) - 1 >= n) break;         // 如果求区间最小值,改为 Math.min() 即可         G.dp[i][j] = Math.max(G.dp[i][j - 1], G.dp[i + (1 << (j - 1))][j - 1]);        }   },    getAns: function(x, y) {     var k = ~~(Math.log(y - x + 1) / Math.log(2));     // 如果求区间最小值,改为 Math.min() 即可     return Math.max(G.dp[x][k], G.dp[y + 1 - (1 << k)][k]);   } };   var a = [1, 3, 2, 4, 8, 7, 6, 5, 9, 0]  // 需要求 RMQ 的数组  G.init(a);   // test cases for (var i = 0; i < 10; i++)   for (var j = i + 1; j < 10; j++) {     var tmp = a.slice(i, j + 1)       , normalAns = Math.max.apply(null, tmp)       , stAns = G.getAns(i, j);      if (normalAns !== stAns)       console.log('Algorithm went wrong!');   }
正文到此结束
Loading...