求解區間最值 - RMQ - ST 算法

解析

ST 算法是 RMQ(Range Minimum/Maximum Query)中一個很經典的算法,它天生用來求得一個區間的最值,但卻不能維護最值,也就是說,過程中不能改變區間中的某個元素的值。O(nlogn) 的預處理和 O(1) 的查詢對於需要大量詢問的場景是非常適用的。接下來我們就來詳細瞭解下 ST 算法的處理過程。

比如有如下長度爲 10 的數組:

複製代碼1 3 2 4 9 5 6 7 8 0  

我們要查詢 [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;
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章