ST表模板

RMQ问题:

RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列a,回答若干询问RMQ(A,i,j)(i, j<=n),返回数列a中下标在i,j之间的最小/大值。如果只有一次询问,那样只有一遍for就可以搞定,但是如果有许多次询问就无法在很快的时间处理出来。在这里介绍一个在线算法。所谓在线算法,是指用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。

ST(Sparse Table)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

ST表模板题:洛谷P3865【模板】ST表:https://www.luogu.com.cn/problem/P3865

题目要求:给定一个静态区间,求出区间最大值。

首先,我们需要进行预处理操作,代码如下:

1 void Rmqint() {
2     for(int i = 1;i <= n;i++) 
3         st[i][0] = a[i];//从i开始长度为1的区间内最大值就为它本身 
4 5     //二重循环求出n个点中起点不同、不同区间中的元素最大值 
6     for(int j = 1;(1 << j) <= n;j++)//枚举不同区间大小,位运算1 << j表示2的j次方;我们需要保证区间大小小于数列长度 
7         for(int i = 1;i + (1 << j) - 1 <= n;i++)//枚举起点,i + (1 << j) - 1表示终点位置,终点位置不能超越数列末端 
8             st[i][j] = max(st[i][j - 1],st[i + (1 << j - 1)][j - 1]);//对两个小区间中最大值较大者即为大区间元素的最大值 
9 }

 

这里我们用一个二维数组st[i][j]来表示从i开始长度为2的j次方的区间中元素的最大值,因为起点为i,长度为2的0次方即1的区间内元素最大值就为他本身,所以我们先给st数组赋上初始值;

对于一个长度为2的j次方的区间,我们可以将其拆分为两个长度为2的j - 1次方的小区间:第一个小区间的起点为i,终点为i + 2的j - 1次方 - 1;第二个小区间的起点为i + 2的j - 1次方,终点为i + 2的j次方 - 1。这里用到了二进制拆分的思想。

如图所示:

 

 

 其次,我们进行的是查询操作,代码如下:

1 int Query(int l,int r) {//l表示区间的左端点,r表示区间右端点 
2     int k = 0;
3     while((1 << k) <= r - l + 1){//找出小于等于区间长度的最大小区间长度 
4         k++;
5     }
6     k--;//while循环最后k多加了一次,需要减去1 
7     return max(st[l][k],st[r - (1 << k) + 1][k]); 
8 }

我们用一个整数k来枚举2的指数,通过上面while循环我们可以找出一个满足2的k次方小于等于区间长度的最大整数k,从而使得拆分出的两个小区间完全覆盖大区间,不会有遗漏的元素,有些元素重复考虑也没关系。

找出了最大整数k,我们即能将大区间拆分为两个小区间:第一个区间起点为L,终点为L + 2的k次方 - 1;第二个区间起点为r - 2的k次方 + 1,终点为r。

图解:

 

 

 PS:两个区间可能有重叠部分,图画得不好

两个核心代码解决了,这道题也就迎刃而解了。完整代码如下:

 

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define maxn 2 * 1000000
 4 int n,m,a[maxn],l[maxn],r[maxn],st[100000][64];//其实2的30次方就够了 
 5 void Rmqint() {
 6     for(int i = 1;i <= n;i++) {
 7         st[i][0] = a[i];//从i开始长度为1的区间内最小值就为它本身 
 8     }
 9     //二重循环求出n个点中起点不同、不同区间中的元素最大值 
10     for(int j = 1;(1 << j) <= n;j++)//枚举不同区间大小,位运算1 << j表示2的j次方;我们需要保证区间大小小于数列长度 
11         for(int i = 1;i + (1 << j) - 1 <= n;i++)//枚举起点,i + (1 << j) - 1表示终点位置,终点位置不能超越数列末端 
12             st[i][j] = max(st[i][j - 1],st[i + (1 << j - 1)][j - 1]);//对两个小区间中最大值较大者即为大区间元素的最大值 
13 }
14 int Query(int l,int r) {//l表示区间的左端点,r表示区间右端点 
15     int k = 0;
16     while((1 << k) <= r - l + 1){//找出小于等于区间长度的最大小区间长度 
17         k++;
18     }
19     k--;//while循环最后k多加了一次,需要减去1 
20     return max(st[l][k],st[r - (1 << k) + 1][k]); 
21 }
22 void Read() {
23     scanf("%d%d",&n,&m);
24     for(int i = 1;i <= n;i++) {
25         scanf("%d",&a[i]);
26     }
27     for(int i = 1;i <= m;i++) {
28         scanf("%d%d",&l[i],&r[i]);
29     }
30 }
31 int main() {
32     Read();
33     Rmqint();
34     for(int i = 1;i <= m;i++) {
35         printf("%d\n",Query(l[i],r[i]));
36     }
37     return 0;
38 }
复制代码

完结撒花

posted @   ほしのかえで  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示