RMQ問題
RMQ(Range Minimum/Maximum Query),即區間最值查詢,是指這樣一個問題:對於一個長度N的數組,在多次詢問中,每次都以O(1)的時間得到區間[a, b]
的最大值或最小值。
在線算法
說來慚愧,到現在纔剛開始清楚說的在線算法和離線算法是什麼意思,所謂在線算法就是說,每次請求及時處理,處理完之後,直接返回,然後等待處理下一次請求。
所以一般在線算法有個預處理過程,預處理數據之後,能夠更快速的處理每次請求的結果,但是會有一個相對長一點的預處理過程。
本文說的ST算法預處理的時間是(NlogN)。
離線算法
所謂離線算法只是在來了非常多的請求之後,一次性處理多個請求,能夠不依賴於預處理數據,卻能一次完成多個請求的結果。
ST ( Sparse Table ) 算法
ST(Sparse Table)算法是一個非常有名的在線處理RMQ問題的算法,它可以在O(nlogn)時間內進行預處理,然後在O(1)時間內回答每個查詢。其思想就是保存以i爲起點的某段數據的最小值。
預處理數據
預處理,用動態規劃(DP)解決。思想接近於二路歸併排序過程的分治思想。
- 既然是DP思想,首先要記錄每步的狀態。
設A[i]是要求區間最值的數列,F[i, j]表示從第i個數起連續2^j個數中的最大值。 這裏的F[i, j]就是每步的狀態。
例如:
A數列爲:
3 2 4 5 6 8 1 2 9 7
F[1,0]
表示第1
個數起,長度爲2^0=1
的最大值,其實就是3
這個數。
同理 :F[1,1] = max(3,2) = 3, F[1,2]=max(3,2,4,5) = 5,F[1,3] = max(3,2,4,5,6,8,1,2) = 8;
- 狀態轉移方程
既然有每步的狀態了,開始找狀態轉移方程。
F[i, j] = max(F[i, j-1], F[i+2^(j-1)][j-1]);
怎麼理解呢?
最初始狀態:F[0, 0] = A[0]; F[1,0] = A[1]; F[2,0] = A[2]; 意思是說每單個元素的最大值都是自己(因爲只有一個元素)
下一個狀態:F[0, 1] = max(F[0,0], F[1,0]) = max(A[0], A[1]);意思是說把F[0, 1] 分成兩段,例如要求得F[3, 2] = max(A[3] , A[4], A[5], A[6]) 其實就是求四個元素的前兩個以及後兩個的最大值。在求F[3,2]的時候已經知道了F[3, 1]和F[5,1]了。
代碼如下:
void RMQ(int num){
for (int j = 1; j < 31; j++) {
for (int i = 0 ; i <= num; i++) {
if (i + (1<<j) -1 <= num) {
maxNum[i][j] = max(maxNum[i][j-1], maxNum[i+(1<<(j-1))][j-1]);
minNum[i][j] = min(minNum[i][j-1], minNum[i+(1<<(j-1))][j-1]);
}
}
}
}
查詢區間
那麼查詢怎麼做呢?畢竟我們存放的都是2次冪個數的最小值。假如查詢的區間是奇數個,或不是2次冪個數怎麼弄?
其實很簡單,就是把頭尾分開!
假如我們需要查詢的區間爲(i,j),那麼我們需要找到小於這個閉區間(左邊界取i,右邊界取j)的最大冪(可以重複,比如查詢5,6,7,8,9,我們可以查詢5678和6789)
- 區間的長度爲
j - i + 1
- 可以取
k=log2( j - i + 1)
區間是1->3 就取log2( 3 - 1 + 1) = 1
小於等於區間的最大冪 RMQ(A, i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}
F[i, k]
是右邊界起點向左找,F[ j - 2 ^ k + 1, k]
是左邊界作爲終點,向右找起點。 一定要用小於等於區間的最大冪是因爲要能夠兩個F加在一起能夠覆蓋整個區間。
完成代碼如下
//
// main.cpp
// RMQ_ST
//
// Created by Alps on 16/5/17.
// Copyright © 2016年 chen. All rights reserved.
//
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
#include <algorithm>
#ifndef MAXARR
#define MAXARR 100 //默認數組最多元素
#endif
using namespace std;
int maxNum[MAXARR][32];
int minNum[MAXARR][32];
void RMQ(int num){
for (int j = 1; j < 31; j++) {
for (int i = 0 ; i < num; i++) {
if (i + (1<<j) -1 < num) {
maxNum[i][j] = max(maxNum[i][j-1], maxNum[i+(1<<(j-1))][j-1]);
minNum[i][j] = min(minNum[i][j-1], minNum[i+(1<<(j-1))][j-1]);
}
}
}
}
int main(int argc, const char * argv[]) {
int M,N;
scanf("%d",&M);
for (int i = 0; i < M; i++) {
scanf("%d",&maxNum[i][0]);
minNum[i][0] = maxNum[i][0]; //初始化狀態
}
RMQ(M);
scanf("%d",&N); //查詢次數
int start = 0, end = 0;
for (int i = 0; i < N; i++) {
scanf("%d%d",&start,&end); //查詢的區間
int k = log2(end-start+1);
cout<<max(maxNum[start][k], maxNum[end-(int)(1<<k)+1][k])<<endl; //最大值
cout<<min(minNum[start][k], minNum[end-(int)(1<<k)+1][k])<<endl; //最小值
}
return 0;
}