参考资料:RMQ问题ST算法
题目来源:#1068 : RMQ-ST算法
一、问题描述:
Range Maximum/Minimum Query(RMQ) 即,给定一整数序列x1,x2,…,xn,编号为1, 2, ..., n,对给定编号区间[L, R],求该区间对应的整数序列中的最大或最小值。
输入:每个测试点(输入文件)有且仅有一组测试数据。每组测试数据的第1行为一个整数N,意义如前文所述。每组测试数据的第2行为N个整数,分别描述每种商品的重量,其中第i个整数表示标号为i的商品的重量weight_i。每组测试数据的第3行为一个整数Q,表示小Hi总共询问的次数。每组测试数据的第N+4~N+Q+3行,每行分别描述一个询问,其中第N+i+3行为两个整数Li, Ri,表示小Hi询问的一个区间[Li, Ri]。对于100%的数据,满足N<=10^6,Q<=10^6, 1<=Li<=Ri<=N,0<weight_i<=10^4。
输出:对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:标号在区间[Li, Ri]中的所有商品中重量最轻的商品的重量。
样例输入:
10
7334
1556
8286
1640
2699
4807
8068
981
4120
2179
5
3 4
2 8
2 4
6 8
7 10
样例输出:
1640
981
1556
981
981
二、问题分析:
最直接的方法是对编号区间[L, R]对应的整数进行遍历,求得最大最小值,此时时间复杂度为O(N),但当查询次数m很大时,时间复杂度为O(nm)。采用 Sparse Table(ST) 算法,可以将查询复杂度降维O(1),而创建ST的预处理操作复杂度仅为O(nlogn)。对最小值问题做如下讨论(对最大值问题只需稍加修改)。
1. 预处理(构建ST):
设f(i, j)表示编号区间[j, j+2^i-1],对应整数序列中的最小值。令整数k满足2^k<=n<2^(k+1),则有:
f(0, j)=xj, 1<=j<=n;
f(i, j)=min{f(i-1, j), f(i-1, j+2^(i-1))}, 1<=i<=k, 0<=j<=n-2^i;
2. 查询:
对编号区间[L, R],设2^i<=(R-L+1)<2^(i+1),则该区间可分解为两个区间[L, 2^i]和[R-2^i+1, R]。故对应整数序列中的最小值为min{f(i, L), f(i, R-2^i+1)}。其中f(i, L)表示编号区间[L, 2^i]对应整数序列中的最小值, f(R-2^i+1, R)表示编号区间[R-2^i+1, R]对应整数序列中的最小值。
三、代码实现:
1. 代码实现1:
#include<cstdio>
#include<cstring>
#define get_m(x,y) ((x)<(y)?(x):(y))
int a[21][1000001];
int main(int argc,char *argv[]){
int n,m,x,y;
scanf("%d",&n);
for(int j=0;j<n;++j){
scanf("%d",*a+j);
}
for(int i=1,t=1;t<n;t<<=1,++i){
for(int j=0;j+(t<<1)<=n;++j){
a[i][j]=get_m(a[i-1][j],a[i-1][j+t]);
}
}
scanf("%d",&m);
while(m--){
scanf("%d%d",&x,&y);
int len=y-x+1;
int i=0,t=1;
while(len!=1){
t<<=1;
len>>=1;
++i;
}
printf("%d\n",get_m(a[i][x-1],a[i][y-t]));
}
return 0;
}
若要求最大值,只需将"#define get_m(x,y) ((x)<(y)?(x):(y))"替换为"#define get_m(x,y) ((x)>(y)?(x):(y))"。
2. 代码实现2:
#include<cstdio>
#include<cstring>
#include<cstdlib>
class RMQ_ST{
public:
RMQ_ST(int *a,int n,bool f);
int query(int x,int y);
~RMQ_ST();
private:
static int get_k(int n);
void build_st(int *a,int n);
int min(int x,int y){
return x<y?x:y;
}
int max(int x,int y){
return x>y?x:y;
}
int size;
int size_k;
int **st;
int (RMQ_ST::*get_m)(int x,int y);
};
RMQ_ST::RMQ_ST(int *a,int n,bool flag_m){
if(a==NULL||n<1){
exit(0);
}
size=n;
size_k=get_k(n);
if(flag_m){
get_m=&RMQ_ST::min;
}else{
get_m=&RMQ_ST::max;
}
if((st=new int*[size_k])==NULL){
exit(0);
}
for(int i=0;i<size_k;++i){
if((st[i]=new int[size])==NULL){
exit(0);
}
}
build_st(a,size);
}
RMQ_ST::~RMQ_ST(){
if(st!=NULL){
for(int i=0;i<size_k;++i){
if(st[i]){
delete[] st[i];
st[i]=NULL;
}
}
delete[] st;
st=NULL;
}
}
int RMQ_ST::query(int x,int y){
if(x<1||y>size||x>y){
exit(0);
}
int k,t=1;
k=get_k(y-x+1)-1;
t<<=k;
return (this->*get_m)(st[k][x-1],st[k][y-t]);
}
void RMQ_ST::build_st(int *a,int n){
for(int j=0;j<n;++j){
st[0][j]=a[j];
}
for(int i=1,t=1;t<n;t<<=1,++i){
for(int j=0;j+(t<<1)<=n;++j){
st[i][j]=(this->*get_m)(st[i-1][j],st[i-1][j+t]);
}
}
}
int RMQ_ST::get_k(int n){
int i=0;
while(n!=0){
n>>=1;
++i;
}
return i;
}
int main(int argc,char *argv[]){
int n,m,x,y;
int *a=NULL;
scanf("%d",&n);
a=new int[n];
for(int j=0;j<n;++j){
scanf("%d",a+j);
}
RMQ_ST rms_st(a,n,true);
scanf("%d",&m);
while(m--){
scanf("%d%d",&x,&y);
printf("%d\n",rms_st.query(x,y));
}
return 0;
}
若求最大值,只需将"RMQ_ST rms_st(a,n,true);"替换为"RMQ_ST rms_st(a,n,false);"。