淺談RMQ算法

前言

這篇文章,是一篇總結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算法

  1. 令A[i]存儲我們要求的數列,F[i][j]表示從第i個位置開始,往後的2^j-1個數字中,最大的數字是多少。
  2. 預處理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))。
  3. 有了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;
}
發佈了39 篇原創文章 · 獲贊 7 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章