前言
這篇文章,是一篇總結RMQ算法的一篇文章,若有疑問或建議,請於下方留言,謝謝!
RMQ是什麼?
RMQ(Range Minimum/Maximum Query),即區間最值查詢。
是指這樣一個問題:對於長度爲n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,j之間的最小/大值。
這兩個問題是在實際應用中經常遇到的問題,而RMQ算法是解決這兩種問題的比較高效的算法。
當然,該問題也可以用線段樹解決,算法複雜度爲:O(N)~O(logN),而線段樹代碼遠遠比我們今天討論的RMQ要長要難理解的多,這裏我們暫不介紹。
我們遇到這種區間最值查詢問題的時候,通常會想到兩種想法
每次查詢時暴力掃描區間,時間複雜度單次查詢O(n),n次查詢便是O(n^2),超過1e4基本上就TLE了
開一個二維數組(空間複雜度O(n*n)),預處理答案在數組中,預處理複雜度O(n^2),單次查詢O(1)。
顯然這些算法都是渣渣,慢的要死,那麼有什麼搞基高級的方法來解決這一類問題呢?
答案是倍增思想。(大概是這樣的)
RMQ算法描述
我們以最大值來詮釋RMQ算法
- 令A[i]存儲我們要求的數列,F[i][j]表示從第i個位置開始,往後的2^j-1個數字中,最大的數字是多少。
- 預處理F數組,對j從大到小枚舉。每次處理F[i][j]的時候,F[i][0~j-1]都已經處理好了,那麼F[i][j]=max(F[i][j-1],F[i+2^(j-1)][j-1]),因爲j最大到log2n,所以預處理的操作的時間複雜度爲O(nlog2(n))。
- 有了F數組啦,現在我們查詢的話就很簡單了。比如說我們查詢區間(i,j),那麼我們令k =⌊log2(i)+j-1⌋,那麼我們查詢的結果就是max(F[i][j],F[j-(2^k)+1][k]),可以明顯看到時間複雜度爲O(1)的,而且保證能夠覆蓋到整個區間。
代碼描述如下
預處理操作
void init_rmq(int m) {
for(int i=1;i<=m;i++)
f[i][0]=a[i];
for(int j=1;(1<<j)<m;j++) {
for(int i=1;i<=m;i++) {
if(i+(1<<j)-1 <= m) {
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
}
查詢操作
int query_rmq(int l,int r) {
int i=0;
for(;(1<<i)+l-1<=r;i++){};
i--;
return min(f[l][i],f[r-(1<<i)+1][i]);
}
RMQ算法的不足
我們發現RMQ算法複雜度竟然如此低,能夠在O(log2(n))的時間內預處理,O(1)的複雜度單點查詢,經常用於優化常數(卡常數是個神奇的東西),看來是一個好東西,但是但是
它不支持修改,只能維護最大最小值,不能維護區間和等會影響答案的東西(此時還是打那騷長的線段樹吧)
RMQ算法例題
這裏給出習題練練手吧
洛谷P1816 忠誠 (傳送門)
題目描述
老管家是一個聰明能幹的人。他爲財主工作了整整10年,財主爲了讓自已賬目更加清楚。要求管家每天記k次賬,由於管家聰明能幹,因而管家總是讓財主十分滿意。但是由於一些人的挑撥,財主還是對管家產生了懷疑。於是他決定用一種特別的方法來判斷管家的忠誠,他把每次的賬目按1,2,3…編號,然後不定時的問管家問題,問題是這樣的:在a到b號賬中最少的一筆是多少?爲了讓管家沒時間作假他總是一次問多個問題。
輸入輸出格式
輸入格式:
輸入中第一行有兩個數m,n表示有m(m<=100000)筆賬,n表示有n個問題,n<=100000。
第二行爲m個數,分別是賬目的錢數
後面n行分別是n個問題,每行有2個數字說明開始結束的賬目編號。
輸出格式:
輸出文件中爲每個問題的答案。具體查看樣例。
輸入輸出樣例
輸入樣例#1:
10 3
1 2 3 4 5 6 7 8 9 10
2 7
3 9
1 10
輸出樣例#1:
2 3 1
解決方案
這題目實在是不能再不能裸的RMQ裸題了,如果沒有完全掌握的RMQ的話還是可以練練手的,或者可以掌握思想後來實現一番,頗有成就感,比線段樹好看多了。。。
代碼
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int size =1e5+10;
int a[size];
int f[size][40];
void init_rmq(int m) {
for(int i=1;i<=m;i++)
f[i][0]=a[i];
for(int j=1;(1<<j)<m;j++) {
for(int i=1;i<=m;i++) {
if(i+(1<<j)-1 <= m) {
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
}
int query_rmq(int l,int r) {
int i=0;
for(;(1<<i)+l-1<=r;i++){};
i--;
return min(f[l][i],f[r-(1<<i)+1][i]);
}
int main() {
int m,n;
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
scanf("%d",&a[i]);
init_rmq(m);
for(int i=1,l,r;i<=n;i++) {
scanf("%d%d",&l,&r);
printf("%d ",query_rmq(l,r));
}
return 0;
}