1. 程式人生 > >RMQ 區間最值查詢演算法

RMQ 區間最值查詢演算法

RMQRange Minimum/Maximum Query,對於長度為n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,j裡的最小(大)值。

主要方法:

  1. 樸素(即搜尋),複雜度為O(n)
  2. 線段樹能在對數時間內在陣列區間上進行更新與查詢。預處理 O(n), 查詢 O(logn)
    定義線段樹在區間[i, j] 上如下: 
    第一個節點維護著區間 [i, j] 的資訊。 
    if i<j , 那麼左孩子維護著區間[i, (i+j)/2] 的資訊,右孩子維護著區間[(i+j)/2+1, j] 的資訊。 
    可知 N  個元素的線段樹的高度 為 [logN] + 1(只有根節點的樹高度為0) . 

    下面是區間 [0, 9]  的一個線段樹: 



    線段樹和堆有一樣的結構, 因此如果一個節點編號為 x ,那麼左孩子編號為2*x  右孩子編號為2*x+1. 

    使用線段樹解決RMQ問題,關鍵維護一個數組M[num],num=2^(線段樹高度+1). 
    M[i]:維護著被分配給該節點(編號:i 線段樹根節點編號:1)的區間的最小值元素的下標。 該陣列初始狀態為-1. 
    #include<iostream>
    
    using namespace std;
    
    #define MAXN 100
    #define MAXIND 256 //線段樹節點個數
    
    //構建線段樹,目的:得到M陣列.
    void initialize(int node, int b, int e, int M[], int A[])
    {
        if (b == e)
            M[node] = b; //只有一個元素,只有一個下標
        else
        {
        //遞迴實現左孩子和右孩子
            initialize(2 * node, b, (b + e) / 2, M, A);
            initialize(2 * node + 1, (b + e) / 2 + 1, e, M, A);
        //search for the minimum value in the first and
        //second half of the interval
        if (A[M[2 * node]] <= A[M[2 * node + 1]])
            M[node] = M[2 * node];
        else
            M[node] = M[2 * node + 1];
        }
    }
    
    //找出區間 [i, j] 上的最小值的索引
    int query(int node, int b, int e, int M[], int A[], int i, int j)
    {
        int p1, p2;
    
    
        //查詢區間和要求的區間沒有交集
        if (i > e || j < b)
            return -1;
    
        //if the current interval is included in
        //the query interval return M[node]
        if (b >= i && e <= j)
            return M[node];
    
        //compute the minimum position in the
        //left and right part of the interval
        p1 = query(2 * node, b, (b + e) / 2, M, A, i, j);
        p2 = query(2 * node + 1, (b + e) / 2 + 1, e, M, A, i, j);
    
        //return the position where the overall
        //minimum is
        if (p1 == -1)
            return M[node] = p2;
        if (p2 == -1)
            return M[node] = p1;
        if (A[p1] <= A[p2])
            return M[node] = p1;
        return M[node] = p2;
    
    }
    
    
    int main()
    {
        int M[MAXIND]; //下標1起才有意義,儲存下標編號節點對應區間最小值的下標.
        memset(M,-1,sizeof(M));
        int a[]={3,1,5,7,2,9,0,3,4,5};
        initialize(1, 0, sizeof(a)/sizeof(a[0])-1, M, a);
        cout<<query(1, 0, sizeof(a)/sizeof(a[0])-1, M, a, 0, 5)<<endl;
        return 0;
    }
    

  3. ST演算法(Sparse Table):它是一種動態規劃的方法。  預處理O(nlogn) 查詢O(1); (不能用來查詢動態區間)
    以最小值為例。a為所尋找的陣列. 
    用一個二維陣列f(i,j)記錄區間[i,i+2^j-1](持續2^j個)區間中的最小值。其中f[i,0] = a[i]; 
    所以,對於任意的一組(i,j),f(i,j) = min{ f( i , j-1 ) , f( i+2^(j-1), j-1) }來使用動態規劃計算出來。 
    這個演算法的高明之處不是在於這個動態規劃的建立,而是它的查詢:它的查詢效率是O(1). 
    假設我們要求a中區間[m,n]的最小值,找到一個數k使得2^k<n-m+1. 

    這樣,可以把這個區間分成兩個部分:[m,m+2^k-1]和[n-2^k+1,n].我們發現,這兩個區間是已經初始化好的. 
    前面的區間是f(m,k),後面的區間是f(n-2^k+1,k). 
    這樣,只要看這兩個區間的最小值,就可以知道整個區間的最小值! 

以下為ST演算法實現最小值查詢。

#include<iostream>
#include<cmath>
using namespace std;

#define MM 100010
#define MAXN 500
#define MAXM 500
int Rmin[MM][20], Rmax[MM][20];
int RminID[MM][20], RmaxID[MM][20];

inline int min(const int &a, const int &b) 
{
	if(a>b) return b;
	return a;
}

inline int max(const int &a,const int &b)
{
	if(a>b)	return  a;
	return b;
}

void makeRmq(int len,int b[])	/*求最值*/ 
{
    int i,j;
    for(i=0; i<len; i++)
        Rmin[i][0] = Rmax[i][0] = b[i];
    for(j=1; (1<<j)<=len; j++)
        for(i=0; i+(1<<j)-1<len; i++)
            {
            	Rmin[i][j] = min(Rmin[i][j-1], Rmin[i+(1<<(j-1))][j-1]);
            	Rmax[i][j] = max(Rmax[i][j-1], Rmax[i+(1<<(j-1))][j-1]);
            }
}

inline int rmqMin(int left,int right)
{
    int k = (int)( log((right-left+1)*1.0) / log(2.0) );
    return min(Rmin[left][k],Rmin[right-(1<<k)+1][k]);
}

inline int rmqMax(int left,int right)
{
	int k = (int)( log((right-left+1)*1.0) / log(2.0) );
    return max(Rmax[left][k],Rmax[right-(1<<k)+1][k]);
}


void makeRmqIndex(int len,int b[]) /*求最值下標*/
{
    int i,j;
    for(i=0; i<len; i++)
        RminID[i][0] = RmaxID[i][0] = i;
    for(j=1; (1<<j)<=len; j++)
        for(i=0; i+(1<<j)-1<len; i++)
            {
            	RminID[i][j] = b[ RminID[i][j-1] ] < b[ RminID[i+(1<<(j-1))][j-1] ] ? RminID[i][j-1] : RminID[i+(1<<(j-1))][j-1];
            	RmaxID[i][j] = b[ RmaxID[i][j-1] ] > b[ RmaxID[i+(1<<(j-1))][j-1] ] ? RmaxID[i][j-1] : RmaxID[i+(1<<(j-1))][j-1];
            }
}

inline int rmqMinIndex(int left,int right,int b[])
{
    int k = (int)( log((right-left+1)*1.0) / log(2.0) );
    return b[ RminID[left][k] ] < b[ RminID[right-(1<<k)+1][k] ] ? RminID[left][k] : RminID[right-(1<<k)+1][k];
}

inline int rmqMaxIndex(int left,int right,int b[])
{
    int k = (int)( log((right-left+1)*1.0) / log(2.0) );
    return b[ RmaxID[left][k] ] > b[ RmaxID[right-(1<<k)+1][k] ] ? RmaxID[left][k] : RmaxID[right-(1<<k)+1][k];
}

int main()
{
    int a[10];
    for(int i=0;i<10;++i) a[i] = i;
    makeRmq(10, a);
	makeRmqIndex(10, a);
    cout<<rmqMaxIndex(0,6,a)<<endl;
    cout<<rmqMaxIndex(4,8,a)<<endl;
    cout<<rmqMax(1,9)<<endl;
    cout<<rmqMax(5,8)<<endl;
    cout<<rmqMinIndex(0,6,a)<<endl;
    cout<<rmqMinIndex(4,8,a)<<endl;
    cout<<rmqMin(1,9)<<endl;
    cout<<rmqMin(5,8)<<endl;
    return 0;
}

二維RMQ,只有min,max ,minID,maxID按照上面1維的改就OK了

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;

/*二維rmq
 *和一維rmq的思路一樣
 *用dp[row][col][i][j]表示(row,col)到(row+2^i,col+2^j)矩形內的最小值
 *查詢的時候
 *  int kx = log(double(x2 - x1 +1)) / log(2.0);
    int ky = log(double(y2 - y1 +1)) / log(2.0);
    int m1 = dp[x1][y1][kx][ky];
    int m2 = dp[x2-(1<<kx)+1][y1][kx][ky];
    int m3 = dp[x1][y2-(1<<ky)+1][kx][ky];
    int m4 = dp[x2-(1<<kx)+1][y2-(1<<ky)+1][kx][ky];
 *取4個值裡面的最小值(有種二分的思想)
 */

const int maxn = 301;

int mat[maxn][maxn];
int Rmin[maxn][maxn][9][9];

inline int max(const int &a,const int &b)
{
	if(a > b) return a;
	return b;
}

inline int min(const int &a,const int &b)
{
	if(a < b) return a;
	return b;
}

void makeRmq_2d(const int rowlen,const int collen)
{
    for(int row = 1 ; row <= rowlen ; row++)
        for(int col = 1 ; col <= collen ; col++)
            Rmin[row][col][0][0] = mat[row][col];
    int t = log((double)collen) / log(2.0);

    for(int i = 0 ; i <= t ; i++)
    {
        for(int j = 0 ; j <= t ; j++)
        {
            if(i == 0 && j == 0)
                continue;
            for(int row = 1 ; row+(1<<i)-1 <= rowlen ; row++)
            {
                for(int col = 1 ; col+(1<<j)-1 <= collen ; col++)
                {
                    if(i == 0)
                    {
                        Rmin[row][col][i][j] = max(Rmin[row][col][i][j-1] , Rmin[row][col+(1<<(j-1))][i][j-1]);
                    }
                    else
                    {
                        Rmin[row][col][i][j] = max(Rmin[row][col][i-1][j] , Rmin[row+(1<<(i-1))][col][i-1][j]);
                    }
                }
            }
        }
    }
}

int query_2d(int x1,int x2,int y1,int y2)
{
    int kx = log(double(x2 - x1 +1)) / log(2.0);
    int ky = log(double(y2 - y1 +1)) / log(2.0);
    int m1 = Rmin[x1][y1][kx][ky];
    int m2 = Rmin[x2-(1<<kx)+1][y1][kx][ky];
    int m3 = Rmin[x1][y2-(1<<ky)+1][kx][ky];
    int m4 = Rmin[x2-(1<<kx)+1][y2-(1<<ky)+1][kx][ky];
    int ans = max( max(m1,m2), max(m3,m4) );
    return ans;
}



int main(int argc, char** argv) 
{
	cout<<"NO DEMO!"<<endl;
	return 0;
}

hdu 2888

#include <stdio.h>
#include <string.h>
#define MAX 301
#define max(a,b) ((a)>(b)?(a):(b))


int flag,power[MAX];
int n,m,q,arr[MAX][MAX];


struct RMQ{

    int dp[MAX][MAX][9][9];
    void Create();
    int Query(int rowl,int rowr,int col,int cor);
}rmq;
void RMQ::Create() {

    int i,j,k,s,limitn,limitm;
    for (i = 1; i <= n; ++i)
        for (j = 1; j <= m; ++j)
            dp[i][j][0][0] = arr[i][j];
    
    
    for (i = 0; i <= power[n]; ++i)
        for (j = 0; j <= power[m]; ++j) {
            
            if (i == 0 && j == 0) continue;
            limitn = n + 1 - (1<<i);
            limitm = m + 1 - (1<<j);
            for (k = 1; k <= limitn; ++k)
                for (s = 1; s <= limitm; ++s) {
                    
                    if (i == 0)
                        dp[k][s][i][j] = max(dp[k][s][i][j-1],dp[k][s+(1<<(j-1))][i][j-1]);
                    else 
                        dp[k][s][i][j] = max(dp[k][s][i-1][j],dp[k+(1<<(i-1))][s][i-1][j]);
                        
                }
        }
}
int RMQ::Query(int r1, int r2, int c1, int c2) {

    int temp = 0;
    int rk = power[r2-r1+1];
    int ck = power[c2-c1+1];


    temp = max(temp,dp[r1][c1][rk][ck]);
    temp = max(temp,dp[r1][c2-(1<<ck)+1][rk][ck]);
    temp = max(temp,dp[r2-(1<<rk)+1][c1][rk][ck]);
    temp = max(temp,dp[r2-(1<<rk)+1][c2-(1<<ck)+1][rk][ck]);
   

    if (temp == arr[r1][c1] || temp == arr[r2][c1]
            || temp == arr[r1][c2] || temp == arr[r2][c2])
        flag = 1;
    return temp;
}
void input (int &a) {

    char c, f;
    while (((c = getchar()) < '0' || f > '9') );
    for (a = 0; c >= '0' && c <= '9'; c = getchar())a = a * 10 + c - '0';
}

int main()
{
    int i,j,k = 0;
    int t,a,b,c,d;
    for (i = 1; i <= 300; ++i)
        if (i < (1<<k)) power[i] = k - 1;
        else power[i] = k,k++;


    while (scanf("%d%d",&n,&m) != EOF) {

        for (i = 1; i <= n; ++i)
            for (j = 1; j <= m; ++j)
                input(arr[i][j]);//scanf("%d",&arr[i][j]);


        rmq.Create();
        //scanf("%d",&q);
        input(q);
        while (q--) {

            input(a),input(b);
            input(c),input(d);
            //scanf("%d%d%d%d",&a,&b,&c,&d);
            flag = 0;
            k = rmq.Query(a,c,b,d);
            if (flag) printf("%d yes\n",k);
            else printf("%d no\n",k);
        }
    }
}



相關推薦

RMQ 區間查詢演算法

RMQ(Range Minimum/Maximum Query),對於長度為n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,j裡的最小(大)值。 主要方法: 樸素(即搜尋),複雜度為O(n)線段樹能在對數時間內在陣列區間上進行

RMQ區間查詢SparseTable演算法

//一維區間最值查詢模板稀疏表sparse table演算法 //區間最值查詢--線段樹--RMQ //區間連續和--線段樹 //區間第k大--快排--劃分樹(線段樹?) --主席樹 //二維區間最值

nyoj 119士兵殺敵(三)(線段樹區間查詢RMQ算法)

信息 include out online log 每次 left 一行 [0 題目119 題目信息 執行結果 本題排行 討論區 士兵殺敵(三) 時間限制:2000 ms | 內存限制:65535 KB 難度:5

區間查詢RMQ

ST演算法 預處理第i位起連續2^k個數的最大值,快速查詢。 說明:定義陣列dp[i][j]表示 從第i位起連續2^j個數 的最大值。 (區間內有2^j個數) 例子: 2 5 9 6 3 1

ST表-求RMQ(區間)問題。

Code: #include<bits/stdc++.h> using namespace std; const int MAX = 2000; int n, m, x, y; int a[MAX + 5], lg[MAX + 5], maxn[MAX + 5][MAX + 5];

hdu 1754 I Hate It(線段樹 單點替換區間 查詢

Problem Description 很多學校流行一種比較的習慣。老師們很喜歡詢問,從某某到某某當中,分數最高的是多少。 這讓很多學生很反感。 不管你喜不喜歡,現在需要你做的是,就是按照老師的要求,寫一個程式,模擬老師的詢問。當然,老師有時候需要更新某位同學的成績。

rmq區間

const int maxn=1000; const int maxb=10;//maxb=log2(maxn) int n; int a[maxn+5]; int d[maxn+5][maxb+5]

(一)線段樹入門--區間查詢

這是一篇入門文章,不過需要你知道啥是二叉樹,並且知道遞迴,本文會持續更新,時間看作者心情。 線段樹 描述 分類:二叉樹搜尋樹 節點結構: struct node { int l,r;//範圍【l,r】 }tr[100];

RMQ區間 倍增求法

void init_rmq(int n) { for(int i=1;i<=n;i++) f[i][0]=a[i]; for(int j=1;j<=20;j++)

RMQ(區間問題)

ont 技術分享 要求 span max image range style 問題 問題:   RMQ (Range Minimum/Maximum Query)問題是指:對於長度為n的數列A,回答若幹詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,

快速查詢區間——RMQ演算法(ST實現)

概述 RMQ(Range Minimum/Maximum Query),即區間最值查詢,是指這樣一個問題:對於長度為n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,j之間的最小/大值。這兩個問題是在實際應用中經常遇到

快速查詢區間——RMQ演算法(線段樹實現程式碼)

要求找出區間內的最大最小值的差。 #include<stdio.h> #include<string.h> #include<math.h> #define lso

hdu1754-I Hate It 線段樹RMQ演算法區間問題

I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total

RMQ(快速查詢區間

#include <stdio.h> #include <stdlib.h> #include <iostream> #include <string.h&g

RMQ區間查詢

pan arp max class spa div light min csharp RMQ復雜度:建表$O\left ( nlgn \right ) $,查詢$O\left ( 1 \right )$ ll F_Min[maxn][20],F_Max[max

【線段樹查詢區間】poj 3264 Balanced Lineup

truct pri scan aps pre sca print logs oid 1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 5 const i

RMQ 區間 頻繁次數

2.0 spa log mes num cst color 順序 max 區間的最大值和最小值 #include <cstdio> #include <cstring> #include <cmath> #include <ios

筆記:RMQ區間)之ST算法

運算 不變 想要 parse 計算機語言 c++ 是我 動態規劃 容易 RMQ(區間最值)之ST算法 RMQ即Range Minimum/Maximun Query 中文意思:查詢一個區間的最小值/最大值 比如有這樣一個數組:A{3 2 4 5 6 8 1 2 9 7},

BZOJ1513:樹套樹之線段樹套線段樹實現二維區間修改和查詢

name names algo getch oid min 投影 協調 pan 我們經常提及的二維線段樹有兩種寫法,一種是四分樹,一種是樹套樹,寫成四分樹的都是神仙。 樹套樹寫法還是比較好理解的,不過要是讓自己硬套的話可能很不容易套出來的 這裏的二維線段樹,外層線段樹是對方

RMQ算法區間

nbsp 覆蓋 main 明顯 else while int 最大值 turn 問題類型:是多次詢問一個大區間裏子區間的最值問題 dp + 位運算的思想處理 rmax[i][j]表示從i開始到i + 2^j - 1的區間裏的最大值dp[i][j] ==== (i,i + 2