浅谈 倍增/ST表
命题描述
给定一个长度为 \(n\) 的序列,\(m\) 次询问区间最大值
分析
上面的问题肯定可以暴力对吧。
但暴力肯定不是最优对吧,所以我们直接就不考虑了。。。
于是引入:倍增
首先,倍增是个什么东西?
在这里转一篇写的超棒的blog,点我。要是这都没看懂你就连小白兔都不如我就无语了。
总的来说,其实就是倒着运用二分的思想,从需求小的慢慢倍增把答案更新到需求大的
ST表就是一种常见的倍增思想的运用
关于ST表
ST表和树状数组,线段树这两种算法一样,是一种用于解决 \(RMQ(Range Minimum/Maximum Query)\)多次区间查询问题的离线算法
ST表的主要思想是构建一个二维数组 \(st[i][j]\),这个二维数组表示需要查询的数组的从下标 \(i\) 到下标 \(i + 2^{j - 1}\) 的最值,这里以最大值为例。超像 \(dp\) 的说~
我们可以把 \([i, j]\) 拆成数量相等的两半,在求出这两部分的最大值即可,故:
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1];
再枚举一下 \(i, j\) 就可以了。点儿都不高级
接下来,该怎么实现查询呢?
如果查询的区间长度刚好是 \(2^k(k为整数)\),直接输出 \(st[l][log(r - l + 1) / log(2)]\) 即可;
如果不是,也很简单,我们还是将所给区间分为两部分。
首先规定在 \(st[i][j]\) 中 \(i = l\),\(j = k\),区间长度 \(len = r - l + 1\)。
会发现最大的\(k\) 应满足 \(2^k <= len\) (这样以 \(l\) 开头的ST表数据覆盖需要查询的区间中的数最多)
所以 \(k = (int) (log(len)/log(2))\)
显然查询时的区间 \([l,r]\) 分成的右边部分的左端点 \(x = r + 1 - 2^p\)
具体实现
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 100005;
int st[MAXN][41];
void read(int &a) { // 读优
int k = 1;
a = 0;
char s = getchar();
while(s < '0' || s > '9') {
if(s == '-') k = -1;
s = getchar();
}
while(s >= '0' && s <= '9') {
a = a * 10 + (s - '0');
s = getchar();
}
a *= k;
}
int main() {
int n, m;
read(n); read(m);
for(int i = 1; i <= n; i++) read(st[i][0]);
for(int j = 1; j <= (int)(log(n) / log(2)); j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++)
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
for(int i = 1; i <= m; i++) {
int l, r;
read(l); read(r);
int k = (int)(log(r - l + 1) / log(2));
printf("%d\n", max(st[l][k], st[r - (1 << k) + 1][k]));
}
return 0;
}