基于倍增稀疏表/线段树的区间最值查询(RMQ)问题

RMQ(Range Minimum/Maximum Query)区间最值查询,即给出长度为n的数组A,以及m组询问s、t(s<=t<=n),返回区间[s,t]中的最值。

用这篇替换掉了以前那篇Naive、漏洞百出的旧博文。


·O(mn)的暴力做法

 

int solve(int s, int t){
  int ans=-INF;
  for(int i=s;i<=t;i++)
  ans=max(ans,a[i]);
  return ans;
}


但这样的复杂度在数据很大的情况下,显然是不足取的。

 


·稀疏表(Sparse Table)

可以利用倍增的思想,通过稀疏表来解决这个问题。

eg.以数组a={3 2 4 5 6 8 1 2 9 7}为例 

用F[k,i]表示从第i个起连续2^k个数中的最大值
那么显然F[0,x]=x

于是有F[1,1]=max(3,2)=3,F[1,3]=max(4,5)=5

而对于F[2,1],则F[2,1]=max(F[1,1],F[1,3])=max(max(3,2),max(4,5))=max(3,5)=5

由此可得:

初值 F[0,i]=a[i]

递推方程 F[k, i]=max(F[k-1,i], F[k-1,i + 2^(k-1)])

 

预处理

类似于DP,时间复杂度为O(nlogn)。代码如下:

 

void init(){ 
  for(i=1; i<=n; i++) f[0][i] = a[i]; 
  for(int k = 0; k <MAXLOGN; k++) 
  for(int i = 1; i <=n; i++){ 
    f[k+1][i]=max(f[k][i],f[k][i+(1<<k)]);
  }
}  

 

 

查询

对于待查询区间[s,t],首先需要找到覆盖这个区间的最小幂区间,其长度可知是t-s+1,取k=log2(s-t+1),那么答案就是max(f[k,s],f[k,t-2^k+1]),其中两个区间互相重叠的部分不影响答案。每次查询只需要O(1)的时间复杂度。

代码如下:

int query(int i, int j){
    int k = (int)(log(t-s+1)/log(2));
    return max(f[k][s], f[k][t-(1<<k)+1]);
}

·线段树

网上资料挺多的,自行搜索看看吧,本蒻也不熟练,嘿嘿。

建树O(n),查询O(logn),相比ST,适合用于n更大,m较小的情况。

这里贴上一段通俗易懂普普通通的完整代码:

 

#include<cstdio>
#define max(a,b) ((a>b)?a:b)
const int MAXN = 200005;
const int INF = 1e9;
int n,m;
int a[MAXN],t[MAXN*3];
void build(int node = 1, int l = 1, int r = n){
     if (l==r) t[node] = a[l]; //到叶子上,则赋值
     else{
        build(node*2, l, (l+r)>>1);  //左儿子
        build(node*2+1, ((l+r)>>1) + 1, r);  //右儿子
        t[node] = max(t[node*2], t[node*2+1]); //回溯赋值
     }
}
int query(int s, int t, int node = 1, int l = 1, int r = n){ //查询区间[s,t],当前查询结点的位置为node,所表示的区间为[l,r],默认node为根结点
     if(l>t||r<s) return -INF; //当前区间与所查询区间无交集,返回一个不影响答案的值
     if(l>=s&&r<=t) return t[node];  //当前区间包含于所查询区间,直接返回当前区间的最值就好了
     int vl = query(s, t, node*2, l, (l+r)>>1); //查询左儿子
     int vr = query(s, t, node*2+1, ((l+r)>>1) + 1, r); //查询右儿子
     return max(vl, vr);
}
int main(){
   scanf("%d", &n);  
   for(int i = 1; i <= n; i++)
      scanf("%d", &a[i]);
   build(); //建树
   scanf("%d", &m);
   for(int i = 1; i <= m; i++){
      int s,t;
      scanf("%d%d", &s, &t);
      printf("%d\n", query(s,t));
   }
   return 0;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值