ST表学习笔记
RMQ问题
RMQ(Range Minimum/Maximum Query)问题是指多次查询某个范围内的最大最小值(或极值),比如对一个序列多次查询区间的最大最小值。
设范围内共有 个元素,查询 次。
朴素算法:
遍历所有范围内的元素,再取最大或最小,则单次查询时间复杂度最
坏为 ,总时间复杂度最坏为:,还是太慢了。
于是就有无数的牛人发明了各种用于 RMQ 问题算法,并且有些算法是可以做到区间修改的,下面这张表格看一下:
算法 | 预处理 | 单次查询 | 单点修改 | 区间修改 |
---|---|---|---|---|
朴素算法 | 无 | |||
分块 | ||||
线段树 | ||||
树状数组 | 结合差分可以做到 | |||
ST 表 | 不支持 | 不支持 |
不难看出,线段树从综合上来看是更厉害的,不过如果只有 RMQ, 那么 ST 表是最厉害的。
ST 表
思想
我们设这个序列是 。
我们可以预处理出所有长度为 的长度的区间的答案,比如对于一个长度为 的区间,我们需要预处理出下图所有彩色区间的最大值:
其中橙色的区间是所有长度为 的区间,蓝色是所有长度为 的区间,绿色是所有长度为 的区间。
我们设 为以 开头,长度为 的区间的最大值,于是在上图中我们发现,两个相邻的橙色区间可以合并成一个蓝色区间,两个相邻的蓝色区间可以合并成一个绿色区间,于是我们就得到了 的递推式:
这样的预处理时间和空间复杂度都是 的,因为总共有 个需要预处理的区间。
所以我们费了这么大劲搞出来一个东西有什么用呢?ST 表可以做到 查询。
举个例子,假设我们要查询第 3 到 8 这段区间的最大值,那么我们需要两个长度不比 大中最大的 2 的幂,且这两个区间能覆盖 。如下图:
黑色区间就是我们选择的区间,长度为 ,其实就是 ,于是对于区间 来说,我们设 , 答案就是:,这样查询就是 的了。
实现
预处理
我们设 ,,于是我们就可以写出预处理代码:
void init() {
lg[1] = 0, pw[0] = 1;
for (int i = 2; i <= n; i++)
lg[i] = lg[i / 2] + 1;
for (int i = 1; i <= 20; i++)
pw[i] = pw[i - 1] * 2;
for (int i = 1; i <= n; i++)
st[i][0] = a[i];
for (int j = 1; pw[j] <= n; j++)
for (int i = 1; i + pw[j] - 1 <= n; i++)
st[i][j] = max(st[i][j - 1], st[i + pw[j - 1]][j - 1]);
}
查询
按照我们之前说过的方法直接查询即可:
int qry(int l, int r) {
int k = lg[r - l + 1];
return max(st[l][k], st[r - pw[k] + 1][k]);
}
一些题目
题目名 【模板】ST 表
题目链接:【模板】ST 表
思路:
模板题,放一下代码。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int MAXN = 1e5 + 5;
const int MAXN_LOG = 20;
int st[MAXN][MAXN_LOG] = {{0}};
int a[MAXN] = {0}, lg[MAXN] = {0}, pw[MAXN_LOG] = {0}, n, m;
void init() {
lg[1] = 0, pw[0] = 1;
for (int i = 2; i <= n; i++)
lg[i] = lg[i / 2] + 1;
for (int i = 1; i <= 20; i++)
pw[i] = pw[i - 1] * 2;
for (int i = 1; i <= n; i++)
st[i][0] = a[i];
for (int j = 1; pw[j] <= n; j++)
for (int i = 1; i + pw[j] - 1 <= n; i++)
st[i][j] = max(st[i][j - 1], st[i + pw[j - 1]][j - 1]);
}
int qry(int l, int r) {
int k = lg[r - l + 1];
return max(st[l][k], st[r - pw[k] + 1][k]);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
init();
for (int i = 1, l, r; i <= m; i++) {
scanf("%d%d", &l, &r);
printf("%d\n", qry(l, r));
}
return 0;
}
[USACO07JAN] Balanced Lineup G
题目链接:[USACO07JAN] Balanced Lineup G
思路:
挺没意思的一道题,记录最大和最小值即可。
代码:提交记录
gcd区间
题目链接:gcd区间
思路:
我们可以把去 ST 表中取最大的操作改成取 ,这样就可以过了。
代码:提交记录
玉米地
题目链接:玉米地
思路:
这里涉及到二维 ST 表。
ST 表也可以是二维的,我们设 表示左上角位置为 ,边长为 的正方形中的最大值(最小值同理,最后相减即可,这里只说最大值),对于 ,我们可以这样更新:
我们通过这张图直观感受一下:
这就是初始化,这样的时间复杂度是 。
查询操作大同小异,我们依然是找到不比边长大中最大的 2 的幂。设我们要找左上角为 ,边长为 的正方形中的最大值,我们设 ,答案就是:
再来一张图直观感受一下:
其实只要有图就好理解了,于是我们就掌握了二维 ST 表。
但这道题有个问题,询问有可能出界,于是我们预处理是直接把边长乘 2,然后把所有不在界里的都设成无穷大或无穷小即可。
代码:提交记录
[JSOI2008] 最大数
题目链接:[JSOI2008] 最大数
思路:
这道题如果不强制在线,我们完全可以离线,等所有数都加完了再去处理询问,但是这道题强制在线,所以我们需要想办法让 ST 表支持修改。
我们发现,如果在末尾新增一个元素,那么所有长度为 2 的幂次方,且末尾为这个元素的都需要更新。但这是新的末尾,所以其实我们之前并没有记录过以上区间的答案,于是我们可以通过查询直接记录即可。由于最多只需要更新 个区间,所以修改是 ,查询是 的。
具体怎么更新,我们假设新的末尾是第 个元素,那么对于某个 满足 ,,而 可以 算出来,于是更新一个值也是 的。
代码:提交记录
FREQUENT - Frequent values
题目链接:FREQUENT - Frequent values
思路:
一道有趣的题目,首先我们要求的依然是极值,所以依然可以用 ST 表,不过我们这次需要对预处理的区间记录以下几个信息:
:这段区间的答案。
:这段区间所有数都相同的最长前缀长度。
:这段区间所有数都相同的最长后缀长度。
:这段区间的左端点。
:这段区间的右端点。
:这段区间的长度。(其实通过 也能直接推出来)
初始化时,把相邻的且长度相同的两个区间合并成一个时,设小区间叫 和 ,则大区间应该这么变:
-
大区间的 。
-
若 ,则 ;
否则 .。 -
若 ,则 ;否则 。
-
若 ,则 ;否则 。
这样依然可以在 的时间里完成预处理。
考虑查询时,我们还是设 ,然后分以下两种情况讨论:
-
若重叠部分的数全部相同,那么答案就是
-
否则答案是:
代码:提交记录
Strip
题目链接:Strip
思路:
这道题很有意思,我们一步一步来。
首先这道题很明显时不能贪心的,于是我们考虑动态规划。我们设 表示前 个元素能分成的最小段数,那么最朴素的更新就是枚举最后选的一段,我们可以用 ST 表 查询极差判断是否能选,于是就有:
但这样是 的,考虑如何优化。
我们发现,当一个区间越来越长,极差单调不降。原因很简单,因为最大值不会变小,最小值不会变大,而极差等于最大值减最小值,于是极差单调不降。
这个有什么好处呢?这就意味着以 结尾的那一段的长度大于等于 ,同时也会小于等于某个数,因为极差有上限。所以我们要找到 之前第一个使得以 结尾的段极差小于等于 的。举个列子,像下图这样:
Dif 就是极差的意思,在上图中,真正能去影响 的是 到 , 长度不够, 往前极差太大,所以:
一般化,我们设 为能影响 的下标中最小的一个,那么其实 就是 。
我们进一步发现对于每个 来说, 也是不降的,于是我们可以搞一个指针指向 ,每次向右移动,最多移动 次,所以时间复杂度是 的。
再考虑如何求 ,这不就是一个 RMQ 吗?而且每次我们会往 数组末尾加一个元素,而 ST 表刚好支持 的末尾添加元素,所以我们在搞一个维护 数组最小值的 ST 表即可。
这道题大致思路就是这样,最终时间复杂度是 的。不过实现是还是有很多细节需要注意。
代码:提交记录
Friends and Subsequences
思路:
这道题也是很有意思的一道题。首先朴素的做法肯定是枚举区间,然后用 ST 表进行 判断。但是复杂度是 的。
我们首先要想到固定一个边界。即,我们去枚举所有 ,尝试去用 的时间复杂度算出有多少 满足题目的限制。我们拿个序列研究一下,当 ,, 以 时为例:
其中相差指的是 的值,不难发现,这个值时单调不降的,于是这就促使我们可以去二分这个值等于 0 使得最小的 和 最大的 ,他们之间(包括自己)各自与 组成的区间都是满足条件的区间,于是这道题我们就可以在 的时间复杂度内求出来了。
代码:提交记录
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!