以下摘自刘汝佳大神的《训练指南》。范围最小值问题(Range Minimum/Maximum Query,RMQ)。给出一个n个元素的数组A1,A2……An,设计一个数据结构,支持查询操作Query(L,R):计算min{A(L),A(L+1),……,A(R)}
Sparse-Table算法能够很好的解决这个问题,它的预处理时间是O(nlogn),但是查询只需要O(1),而且常数很小。最重要的是,这个算法非常好写,并且不易写错。
令d[i,j)表示从i开始的,长度为2j的一段元素中的最小值,则可以用递推的方法计算d(i,j):d(i,j)=min{d(i,j-1),d(i+2j – 1,j-1)}。注意2j<=n,因此d数组元素的个数不超过nlogn,而每一项都可以在常数时间计算完毕,故总时间为O(nlogn)。代码如下。
void RMQ_init() { int i,j; for(i=0; i<n; i++) d[i][0]=a[i]; for(j=1; (1<<j)<=n; j++) for(i=0; i+(1<<j)-1<n; i++) d[i][j]=max(d[i][j-1],d[i+(1<<(j-1))][j-1]); }
查询操作很简单,令k为满足2k<=R-L+1的最大整数,则以L开头、以R结尾的两个长度为2k的区间合起来即覆盖了查询区间[L,R]。由于是取最小值,有些元素重复考虑了几遍也没关系。
查询代码如下。
int RMQ(int L,int R) { int k=0; while((1<<(k+1))<=R-L+1) k++; return min(d[L][k],d[R-(1<<k)+1][k]); }
POJ3264 Balanced Lineup
给定一个序列A1,A2,…An-1,An。并给出一些查询,对于每次查询(L,R),要求你返回序列[L,R]中最小值与最大值的差
赤裸裸的RMQ。。。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define MAXN 50005 using namespace std; int a[MAXN],n; int ma[MAXN][20],mi[MAXN][20]; void RMQ_init() { int i,j; for(i=0;i<n;i++) { ma[i][0]=a[i]; mi[i][0]=a[i]; } for(j=1;(1<<j)<=n;j++) for(i=0;i+(1<<j)-1<n;i++) { ma[i][j]=max(ma[i][j-1],ma[i+(1<<(j-1))][j-1]); mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]); } } int RMQ(int L,int R) { int k=0; while((1<<(k+1))<=(R-L+1)) k++; return max(ma[L][k],ma[R-(1<<k)+1][k])-min(mi[L][k],mi[R-(1<<k)+1][k]); } int main(void) { int i,Q,l,r; memset(mi,0,sizeof(mi)); memset(ma,0,sizeof(ma)); scanf("%d%d",&n,&Q); for(i=0;i<n;i++) scanf("%d",&a[i]); RMQ_init(); while(Q--) { scanf("%d%d",&l,&r); printf("%d\n",RMQ(l-1,r-1)); } return 0; }
POJ2019 Cornfields
给定一个大小为N*N矩阵,给出一些查询,对于每次查询(x,y),要求你返回以左下角(x,y)开始的长宽都为B的子矩阵中的最大值与最小值的差
最标准的二维RMQ
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define MAXN 255 using namespace std; int Min[MAXN][MAXN][9][9],Max[MAXN][MAXN][9][9],a[MAXN][MAXN]; int n; void init_RMQ() { int i,j,ii,jj; for(i=1; i<=n; i++) for(j=1; j<=n; j++) { Min[i][j][0][0]=a[i][j]; Max[i][j][0][0]=a[i][j]; } for(j=0; (1<<j)<=n; j++) for(jj=0; (1<<jj)<=n; jj++) { if(j==0&&jj==0) continue; for(i=1; i+(1<<j)-1<=n; i++) for(ii=1; ii+(1<<jj)-1<=n; ii++) { if(j==0) { Min[i][ii][j][jj]=min(Min[i][ii][j][jj-1],Min[i][ii+(1<<(jj-1))][j][jj-1]); Max[i][ii][j][jj]=max(Max[i][ii][j][jj-1],Max[i][ii+(1<<(jj-1))][j][jj-1]); } else { Min[i][ii][j][jj]=min(Min[i+(1<<(j-1))][ii][j-1][jj],Min[i][ii][j-1][jj]); Max[i][ii][j][jj]=max(Max[i+(1<<(j-1))][ii][j-1][jj],Max[i][ii][j-1][jj]); } } } } int RMQ(int L1,int R1,int L2,int R2) { int k=0,max1,max2,min1,min2; while(1<<(k+1)<=L2-L1+1) k++; max1=max(Max[L1][R1][k][k],Max[L2-(1<<k)+1][R1][k][k]); max2=max(Max[L1][R2-(1<<k)+1][k][k],Max[L2-(1<<k)+1][R2-(1<<k)+1][k][k]); min1=min(Min[L1][R1][k][k],Min[L2-(1<<k)+1][R1][k][k]); min2=min(Min[L1][R2-(1<<k)+1][k][k],Min[L2-(1<<k)+1][R2-(1<<k)+1][k][k]); return max(max1,max2)-min(min1,min2); } void init() { int i,k,j,b,x,y; while(scanf("%d%d%d",&n,&b,&k)!=EOF) { for(i=1; i<=n; i++) for(j=1; j<=n; j++) scanf("%d",&a[i][j]); init_RMQ(); while(k--) { scanf("%d%d",&x,&y); printf("%d\n",RMQ(x,y,x+b-1,y+b-1)); } } } int main(void) { init(); return 0; }