ST表学习笔记
RMQ问题
RMQ(Range Minimum/Maximum Query)问题是指多次查询某个范围内的最大最小值(或极值),比如对一个序列多次查询区间的最大最小值。
设范围内共有
朴素算法:
遍历所有范围内的元素,再取最大或最小,则单次查询时间复杂度最
坏为
于是就有无数的牛人发明了各种用于 RMQ 问题算法,并且有些算法是可以做到区间修改的,下面这张表格看一下:
| 算法 | 预处理 | 单次查询 | 单点修改 | 区间修改 |
|---|---|---|---|---|
| 朴素算法 | 无 | |||
| 分块 | ||||
| 线段树 | ||||
| 树状数组 | 结合差分可以做到 |
|||
| ST 表 | 不支持 |
不难看出,线段树从综合上来看是更厉害的,不过如果只有 RMQ, 那么 ST 表是最厉害的。
ST 表
思想
我们设这个序列是
我们可以预处理出所有长度为
其中橙色的区间是所有长度为
我们设
这样的预处理时间和空间复杂度都是
所以我们费了这么大劲搞出来一个东西有什么用呢?ST 表可以做到
举个例子,假设我们要查询第 3 到 8 这段区间的最大值,那么我们需要两个长度不比
黑色区间就是我们选择的区间,长度为
实现
预处理
我们设
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 表可以进行
包含这个点的且长度为
一些题目
题目名 【模板】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 表,不过我们这次需要对预处理的区间记录以下几个信息:
2) 否则答案是:
代码:提交记录
Strip
题目链接:Strip
思路:
这道题很有意思,我们一步一步来。
首先这道题很明显时不能贪心的,于是我们考虑动态规划。我们设
但这样是
我们发现,当一个区间越来越长,极差单调不降。原因很简单,因为最大值不会变小,最小值不会变大,而极差等于最大值减最小值,于是极差单调不降。
这个有什么好处呢?这就意味着以
Dif 就是极差的意思,在上图中,真正能去影响
一般化,我们设
我们进一步发现对于每个
再考虑如何求
这道题大致思路就是这样,最终时间复杂度是
代码:提交记录
Friends and Subsequences
题目链接:Friends and Subsequences
思路:
这道题也是很有意思的一道题。首先朴素的做法肯定是枚举区间,然后用 ST 表进行
我们首先要想到固定一个边界。即,我们去枚举所有
其中相差指的是
代码:提交记录