RMQ refers to Range Minimum Question
ST表是一種數據結構,專門解決RMQ問題
目录
What is RMQ ?
從字面意義上理解RMQ有點粗糙,因為這裡要跟ST做連結,所以我們給他來點定義。
定義一: 符合結合律
定義二:允許區間重疊不影響結果
經典列子:
1.
2.
3. a & b & c = a & c & b = a & (b & c)
反例:
對於求和區間一旦重合就會影響答案
一個非常理論的算法,就是利用RMQ求出最近公共祖先。
但我不會。。。
What is ST表 ?
ST表你可以把他當成是查詢RMQ為O(1)的算法。
我們先不要理什麼ST表什麼的。
如果要讓你設計一個算法要O(1)的時間複雜度查詢,你會怎麽做?
最簡單,我們開個表,把所有區間的可能性和答案都存下來。
for(int i = 1; i <= n; i++)f[i][1]=a[i];
for(int i = 1; i <= n; i++){
for(int j = 2; j <= n; j++){
f[i][j] = min(f[i][j - 1], a[j]);
}
}
時間複雜度:O( n^2)
空間複雜度:O(n^2)
查詢複雜度:O(1)
但你可以發現上面的表,其實有很多浪費時間和空間的地方,因為上面的表即便在區間重疊後,還是去算一遍重疊區間的最大或最小值,這其實沒有必要,因為根據經典列子1,可以知道我們其實沒有必要去算重疊區間。
然後利用倍增思想,只初始化2^j 的區間長度的值,查詢的時候利用一些trick把問題轉為經典列子1就行了。
所以現在我們的 f(i, j) 是指以 i 為首 i + 2^j 為結尾的區間最值。
那麼狀態怎麼轉移呢?根據定義可以知道
1.
2.
對於第二條妳會問為什麼?
對於區間長度為4的最小值,必然由兩個區間長度為2的轉化而來。
對於區間長度為8的最小值,必然由兩個區間長度為4的轉化而來。如此類推。
畫個圖給你看看。
for(int i = 1; i <= n; i++){
//因為數組從index 1 開始所以要減1
for(int j = 1; i + (1<<j) - 1; j++){
f[i][j] = max(f[i][j - 1], f[i + (1<<(j - 1))][j - 1]);
}
}
那這裡的初始化的時間和空間就變成O(logn),是指數級的下降。
那要如何查詢呢?
因為我們都是按照2的次方長度來定義區間的,所以對於要查詢的區間,我們首先要得到他的區間長度,並且對他求Log2,把right 減掉這個值再讓left 加上這個值,再去看看表內那一個區間的最值大一點就可以了。
代碼實現query
int query(int lft, int rght){
int k = log2(rght-left+1);
return min(f[lft][k], f[rght - (1<<k) + 1][k]);
}
———————————————————————————————————————————所以我們的二維數組只需要花費 n * log2(n) 的空間,
整體代碼實現
#include <iostream>
#include <cmath>
using namespace std;
const int MAXN = 1e6 + 10;
int n, m;
int st[MAXN][21];
void init(){
for(int j = 1; i <= 21; j++){
for(int i = 1; i+(1<<j)-1 <= n; i++){
f[i][j] = min(st[i][j - 1], st[i + (1<<(j - 1))][j - 1]);
}
}
}
int query(int lft, int rght){
int k = log2(rght - lft + 1);
return min(st[lft][k], st[rght-(1<<k)+1][k]);
}
int main(){
cin >> n;
cin >> m;
for(int i = 1; i <= n; i++){
cin >> st[i][0];
}
init();
int m;
while(m--){
int lft, rght;
cin >> lft >> rght;
cout << query(lft, rght) << endl;
}
return 0;
}