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; }