RMQ(Range Minimum Query)

         范围最小值(最大值)问题,给出一个n个元素的数组A1,A2,A3,···,An,通过RMQ这个数据结构能够快速的进行查询操作Query(L, R):计算min(AL,AL+1,···,AR)。

这类问题通常用ST算法(Sparse Table):

来看一下ST算法是怎么实现的(以最大值为例):
首先是预处理,用一个DP解决。设a是要求 区间最值的 数列,f[i,j]表示从第i个数起连续2^j个数中的最大值。例如数列3 2 4 5 6 8 1 2 9 7 ,f[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。f[1,2]=5,f[1,3]=8,f[2,0]=2,f[2,1]=4……从这里可以看出f[i,0]其实就等于a[i]。这样,DP的状态、初值都已经有了,剩下的就是 状态转移方程。我们把f[i,j](j≥1)平均分成两段(因为j≥1时,f[i,j]一定是偶数个数字),从i到i+2^(j-1)-1为一段,i+2^(j-1)到i+2^j-1为一段(长度都为2^(j-1))。用上例说明,当i=1,j=3时就是3,2,4,5 和6,8,1,2这两段。f就是这两段的最大值中的最大值。于是我们得到了动规方程F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1])。
接下来是得出最值,也许你想不到计算出f有什么用处,一般要想计算max还是要O(logn),甚至O(n)。但有一个很好的办法,做到了O(1)。还是分开来。如在上例中我们要求区间[2,8]的最大值,就要把它分成[2,5]和[5,8]两个区间,因为这两个区间的最大值我们可以直接由f[2,2]和f[5,2]得到。扩展到一般情况,就是把区间[l,r]分成两个长度为2^n的区间(保证有f对应)。直接给出表达式:
k:=trunc(ln(r-l+1)/ln(2));
ans:=max(F[l,k],F[r-2^k+1,k]);
这样就计算了从l开始,长度为2^k的区间和从r-2^k+1开始长度为2^k的区间的最大值(表达式比较繁琐,细节问题如加1减1需要仔细考虑),二者中的较大者就是整个区间[l,r]上的最大值。
ST算法代码如下:
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
#define maxn 100005
int dp[maxn][20], a[maxn], d[maxn][20];
void Init(int n)
{
      for (int i=0; i<n; ++i)
      {
            dp[i][0] = a[i];
            d[i][0] = a[i];
      }
      for (int j=1; (1<<j)<=n; ++j)//(1<<j)为长度,长度不超过n
            for (int i=0; i+(1<<j)-1<n; ++i)//i+(1<<j)-1(上限不超过n)
            {
                  dp[i][j] = min( dp[i][j-1], dp[ i+(1<<(j-1)) ][j-1] );
                  d[i][j] = max( d[i][j-1], d[ i+(1<<(j-1)) ][j-1] );
            }
}
int Query(int l, int r)
{
      //2^k + 2^k<=r-l+1
      int k = 0;
      while ((1<<(k+1))<=r-l+1)
            ++k;
      //k=(int)log2(r-l+1);
      return max( d[l][k], d[r-(1<<k)+1][k] )-min( dp[l][k], dp[r-(1<<k)+1][k] );
}
int main()
{
      int n, m, l, r;
      while (~scanf("%d%d", &n, &m))
      {
            for (int i=0; i<n; ++i)
                  scanf("%d", &a[i]);
            Init(n);
            while (m--)
            {
                  scanf("%d%d", &l, &r);
                  printf("%d\n", Query(l-1, r-1));
            }
      }
      return 0;
}

线段树代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
#define maxn 100005
#define max(x, y) x>y?x:y
#define min(x, y) x>y?y:x
struct node
{
      int l, r;
      int min, max;
}v[maxn<<2];
void build (int l, int r, int n)
{
      v[n].l = l;
      v[n].r = r;
      if (l == r)
      {
            scanf("%d", &v[n].min);
            v[n].max = v[n].min;
            return ;
      }
      int mid = (v[n].l + v[n].r) >> 1;
      build(l, mid, n<<1);
      build(mid+1, r, n<<1|1);
      v[n].max = max( v[n<<1].max, v[n<<1|1].max );
      v[n].min = min( v[n<<1].min, v[n<<1|1].min );
}
int query(int l, int r, int n, int& Max)
{
      if (l<=v[n].l && v[n].r<=r)
      {
            Max = max( Max, v[n].max );
            return v[n].min;
      }
      int mid = (v[n].l + v[n].r) >> 1;
      if (r<=mid)
      {
            int t1, t2 = -1;
            t1 = query(l, r, n<<1, t2);
            Max = max(t2, Max);
            return t1;
      }
      else if (l > mid)
      {
            int t1, t2 = -1;
            t1 = query(l, r, n<<1|1, t2);
            Max = max(t2, Max);
            return t1;
      }
      else
      {
            int t1, t2, t3 = -1, t4 = -1;
            t1 = query(l, mid, n<<1, t3);
            t2 = query(mid+1, r, n<<1|1, t4);
            Max = max(t3, t4);
            return min( t1, t2 );
      }
}
int main()
{
      int n, m, Max, Min, a, b;
      while (~scanf("%d%d", &n, &m))
      {
            build(1, n, 1);
            while (m--)
            {
                  scanf("%d%d", &a, &b);
                  Max = -1;
                  Min = query(a, b, 1, Max);
                  printf("%d\n", Max-Min);
            }
      }
      return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值