RMQ算法(Range Minimum/Maximum Query) 是求区间极值的高效算法,依据所需实现的不同性能可以有多种写法,这里主要讲基于线段树和稀疏表(Sparse Table)的两种方法
线段树是维护区间的一类高效数据结构,依据这个特性,我们可以用线段树实现RMQ算法,用线段树实现的RMQ算法不仅可以查询区间最小值,还可以更改某个节点的值
#include
using namespace std;
typedef long long int LL;
const int MAXN=18;
const int INF=1e9;
int num[2*(1<int n;
void init(int base){
n=1;
while(n 1;
}
for(int i=0;i<2*n-1;i++){
num[i]=INF;
}
}
void change_k(int k,int a){//将节点k的值赋为a
k+=n-1;
num[k]=a;
while(k){
k=(k-1)>>1;
num[k]=min(num[2*k+1],num[2*k+2]);
}
}
int get_min(int a,int b,int k,int l,int r){
if(a<=l&&b>=r) return num[k];
else if(a>=r||b<=l) return INF;
else {
// cout<
// 递归至左右子树求解
int lc=get_min(a,b,2*k+1,l,(l+r)/2);
int rc=get_min(a,b,2*k+2,(l+r)/2,r);
return min(lc,rc);
}
}
int main(){
scanf("%d",&n);
int cnt=n;
init(n);
for(int i=0;iint tmp;
scanf("%d",&tmp);
change_k(i,tmp);
}
int a,b;
scanf("%d %d",&a,&b);
/*
int k,num;
scanf("%d %d",&k,&num);
change_k(k,num);
这里还可以实现更改
*/
printf("%d\n",get_min(a,b,0,0,n)==INF?-1:get_min(a,b,0,0,n));
}
预处理时,操作的节点个数为n+n/2+n/4+……,约为2n个,所以复杂度为O(n),每一次查询与修改区间值的复杂度是O(log n).
线段树的查询复杂度为O(log n),对于有多组询问的题还是太慢,有了线段树实现的铺垫,我们思考,是否有一种方法能预先处理出区间极值呢,答案是有的,就是ST表
#include
using namespace std;
const int MAXN=18;
int dp[(1<int a[(1<int n;
void st_init(){
for(int i=0;i0]=a[i];
}
for(int j=1;(1<for(int i=0;i+(1<1],dp[i+(1<<(j-1))][j-1]);
}
}
}
int get_min(int l,int r){
int k=(int)(log((double)r-l+1)/log(2.0));
return min(dp[l][k],dp[r-(1<1][k]);
}
int main(){
scanf("%d",&n);
for(int i=0;iscanf("%d",&a[i]);
}
st_init();
int l,r;
scanf("%d %d",&l,&r);
printf("%d\n",get_min(l,r));
}
显而易见,预处理时的复杂度是O(n log n)的,而查询的复杂度为O(1),可以高效解决多组询问的题目,但基于ST表的写法无法对区间的值进行高效更新.