RMQ——询问区间最大最小值问题

RMQ

如题:作用是询问区间最大最小值问题

步骤:

1.定义

a[i]表示数列的数

lg数组是一个辅助数组,用于快速计算查询区间的长度对应的k值。具体来说,lg[i]表示以2为底,i的对数。在C++中,可以使用lg2函数来计算以2为底的对数

f[i][j]表示从a[i]到a[i+2^i-1]这个范围内的最大值,也就是以a[i]为起点连续2^i个数的最大值

2.初始化

初始化lg[0] = -1,这样才能使lg[1] = 0

预处理出长度为1-n的lg值

计算f[i][j]

void rmq()
{
    lg[0] = -1;
    for(int i=1;i<=n;i++)
        f[i][0] = a[i],lg[i] = lg[i>>1]+1; //预处理出长度为1-n的lg值
    for(int j=1;j<=lgn;j++) //计算f[i][j]
        for(int i=1;i+(1<<j)-1<=n;i++) //区间边界不超过n
            f[i][j] = max(f[i][j-1],f[i+(1<<j-1)][j-1]);
}

3.询问

对于求区间[x,y]的最大值,直接按照下面的表达式计算即可

s = lg[y-x+1]

ans = max(f[x][s],f[y-(1<<s)+1][s])

     int s = lg[y-x+1]; //求lg2(y-x+1)下取整的值 
     printf("%d\n",max(f[x][s],f[y-(1<<s)+1][s]));

RMQ演示过程:

假设我们有一个序列a = [1, 5, 2, 4, 6, 3, 7, 8],我们需要构建一个ST表(Sparse Table)来进行RMQ(Range Maximum Query)查询。

首先,我们初始化ST表,使得对于所有的i,f[i][0] = a[i]。这表示所有长度为1的区间的最大值就是它们自身。我们的ST表现在看起来像这样:

f = [
  [1, ?, ?, ?, ?, ?, ?, ?],
  [5, ?, ?, ?, ?, ?, ?, ?],
  [2, ?, ?, ?, ?, ?, ?, ?],
  [4, ?, ?, ?, ?, ?, ?, ?],
  [6, ?, ?, ?, ?, ?, ?, ?],
  [3, ?, ?, ?, ?, ?, ?, ?],
  [7, ?, ?, ?, ?, ?, ?, ?],
  [8, ?, ?, ?, ?, ?, ?, ?]
]

接下来,我们填充ST表的其他部分。对于每个j > 0,我们有f[i][j] = max(f[i][j-1], f[i+2^(j-1)][j-1])。这表示一个长度为2^j的区间的最大值可以由两个长度为2^(j-1)的区间的最大值得到。这两个区间分别是从i开始,和从i+2^(j-1)开始。我们的ST表现在看起来像这样:

f = [
  [1, 5, 5, 6, 7, 7, 8, 8],
  [5, 5, 5, 6, 7, 7, 8, 8],
  [2, 4, 6, 6, 7, 7, 8, 8],
  [4, 6, 6, 7, 7, 8, 8, 8],
  [6, 6, 7, 7, 8, 8, 8, 8],
  [3, 7, 7, 8, 8, 8, 8, 8],
  [7, 7, 8, 8, 8, 8, 8, 8],
  [8, 8, 8, 8, 8, 8, 8, 8]
]

现在,我们可以使用ST表来进行RMQ查询。例如,如果我们要查询区间[2, 5]的最大值,我们首先找到最大的j,使得2^j <= 5-2+1 = 4。在这个例子中,j = 2。然后,我们可以得到区间[2, 5]的最大值就是max(f[2][2], f[5-2^2+1][2]) = max(5, 6) = 6。

 

6570: 数列区间最大值 

描述

 

输入一串数字,给你 M 个询问,每次询问就给你两个数字 X,Y,要求你说出 X 到 Y 这段区间内的最大数。

 

 

输入

 

第一行两个整数 N,M 表示数字的个数和要询问的次数;

接下来一行为 N 个数;

接下来 M 行,每行都有两个整数 X,Y。

对于全部数据,1≤N≤105,1≤M≤106,1≤X≤Y≤N。数字不超过 C/C++ 的 int 范围。

 

 

输出

 

输出共 M 行,每行输出一个数。

 

 

样例输入

 

10 2
3 2 4 5 6 8 1 2 9 7
1 4
3 8

样例输出

5
8

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10,inf = 0x3f3f3f3f,lgn = 20;
int lg[N],f[N][lgn+5],a[N];
int n,m,x,y;
void rmq()
{
    lg[0] = -1;
    for(int i=1;i<=n;i++)
        f[i][0] = a[i],lg[i] = lg[i>>1]+1; //预处理出长度为1-n的lg值
    for(int j=1;j<=lgn;j++) //计算f[i][j]
        for(int i=1;i+(1<<j)-1<=n;i++) //区间边界不超过n
            f[i][j] = max(f[i][j-1],f[i+(1<<j-1)][j-1]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    rmq(); //预处理rmq 
    while(m--)
    {
        scanf("%d%d",&x,&y);
        int s = lg[y-x+1]; //求lg2(y-x+1)下取整的值 
        printf("%d\n",max(f[x][s],f[y-(1<<s)+1][s]));
    } 
     return 0;
}

 AI优化后的模板注释:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10,inf = 0x3f3f3f3f,lgn = 20; //定义常量,N是序列的最大长度,inf是一个很大的数,lgn是对数的上限
int lg[N],f[N][lgn+5],a[N]; //lg数组用于存储对数值,f数组是稀疏表,a数组是输入的序列
int n,m,x,y; //n是序列的长度,m是查询的次数,x和y是查询的区间

void rmq() //预处理函数,用于构建稀疏表
{
    lg[0] = -1;
    for(int i=1;i<=n;i++)
        f[i][0] = a[i],lg[i] = lg[i>>1]+1; //初始化稀疏表的第0列和lg数组
    for(int j=1;j<=lgn;j++) //计算稀疏表的其他列
        for(int i=1;i+(1<<j)-1<=n;i++) //确保区间在序列范围内
            f[i][j] = max(f[i][j-1],f[i+(1<<j-1)][j-1]); //根据状态转移方程计算f[i][j]
}

int main()
{
    scanf("%d%d",&n,&m); //读入序列长度和查询次数
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]); //读入序列
    rmq(); //预处理
    while(m--)
    {
        scanf("%d%d",&x,&y); //读入查询区间
        int s = lg[y-x+1]; //计算对数值
        printf("%d\n",max(f[x][s],f[y-(1<<s)+1][s])); //根据稀疏表查询区间最大值
    } 
     return 0;
}

 

posted @ 2023-06-01 17:06  CRt0729  阅读(35)  评论(0编辑  收藏  举报