RMQ(Range Minimum/Maximum Query),对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值。
主要方法:
- 朴素(即搜索),复杂度为O(n)
- 线段树能在对数时间内在数组区间上进行更新与查询。预处理 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; }
- 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);
}
}
}