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);
        }
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值