初见 | 数据结构 | ST表
前言
在说ST表之前,我们先需要知道ST表是什么个东西
按照 OI WIKI 上说的,ST表是解决可重复贡献问题的数据结构
至于可重复贡献问题的解释,下面我引用 OI WIKI 上的解释
什么是可重复贡献问题?
可重复贡献问题,是指对于运算 \(\text{opt}\) ,满足 \(x \ \text{opt} \ x = x\) ,则对应的区间询问就是一个可重复贡献问题。例如,最大值有 \(\max(x,x) = x\) ,gcd 有 \(\gcd(x,x) = x\) ,所以 RMQ 和区间 GCD 就是一个可重复贡献问题。像区间和就不具有这个性质,如果求区间和的时候采用的预处理区间重叠了,则会导致重叠部分被计算两次,这是我们所不愿意看到的。另外, 还必须满足结合律才能使用 ST 表求解。
上面提到的 RMQ 是常见的要有ST表解决的问题
关于 RMQ 自然就是:
好吧,其实是区间最值问题,看洛谷的板子题的题解来看,这里ST表应该是解决解决静态区间的 RMQ 问题,下面就根据板子题来简单说下ST表如何区间查询求最大值
例题
这里的例题是[洛谷P3865 ST表]
大体思路
ST表基于倍增的思想,有着 \(O(n\log n)\) 的预处理,同时它可以 \(O(1)\) 的回答每一个询问,但由于它只厨力静态区间,于是它没有办法做到区间修改
上面说到,最大值是可重复贡献问题,因此区间在查询的时候出现覆盖是不会对答案的正确性有影响的,根据这一性质,查阅资料手模可以发现,我们至多需要两个区间来覆盖我们所查询的区间
具体解决方案
设二元组 \(a(i,j)\) 表示区间 \([i,i+2^j-1]\) 的最大值,设这个序列为 \(k\)
根据这个定义,我们可以发现一个切入口,即 \(a(i,0)=k_i\)
同时,根据倍增的思想可以发现第二维相当于我们倍增的时候跳了 \(2^j-1\) 步,因此我们可以写出状态转移方程
然后对于每个查询,直接简单的把 \([l,r]\) 分成 \([l,l+2^x-1]\) 和 \([r-(2^x-1),r]\) 两块,这里的 \(x = \left \lfloor \log_2 (r-l+1) \ \right \rfloor\)
后面那段区间的分法,建议自己手模一下理解,或者问一下大佬们,我问了 Dfkuaid 明白了,但是我说不出来(?)
Code
下面放上这个模板题的代码
#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define S signed
#define U unsigned
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
#define mst(a, b) memset(a, b, sizeof(a))
#define ON std::ios::sync_with_stdio(false)
using namespace std;
I void fr(LL & x)
{
LL f = 1;
char c = getchar();
x = 0;
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 3) + (x << 1) + c - '0';
c = getchar();
}
x *= f;
}
I void fw(LL x)
{
static LL stak[35];
LL top=0;
do
{
stak[top++]=x%10;
x/=10;
}
while(x);
while(top) putchar(stak[--top]+'0');
putchar('\n');
}
const int MXX=1e5+5;
LL a[MXX][21],n,m,l,r;
I LL Q(LL l,LL r)
{
LL temp=log2(r-l+1);
Heriko max(a[l][temp],a[r-(1<<temp)+1][temp]);
}
S main()
{
fr(n),fr(m);
for(R LL i=1;i<=n;i++) fr(a[i][0]);
for(R LL _=1;_<=21;_++)
for(R LL i=1;i+(1<<_)-1<=n;i++)
a[i][_]=max(a[i][_-1],a[i+(1<<(_-1))][_-1]);
for(R LL i=1;i<=m;i++) fr(l),fr(r),fw(Q(l,r));
Heriko Deltana;
}
需要注意的是,我这里用的是 log2()
这个函数,其实是偷懒,应当预处理到数组里,具体方式可以参见下面 OI WIKI 的Code
//HD再次标明出处 :OI WIKI
#include <bits/stdc++.h>
using namespace std;
const int logn = 21;
const int maxn = 2000001;
int f[maxn][logn + 1], Logn[maxn + 1];
inline int read() {
char c = getchar();
int x = 0, f = 1;
while (c < '0' || c > '9') {
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}
void pre() {
Logn[1] = 0;
Logn[2] = 1;
for (int i = 3; i < maxn; i++) {
Logn[i] = Logn[i / 2] + 1;
}
}
int main() {
int n = read(), m = read();
for (int i = 1; i <= n; i++) f[i][0] = read();
pre();
for (int j = 1; j <= logn; 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]);
for (int i = 1; i <= m; i++) {
int x = read(), y = read();
int s = Logn[y - x + 1];
printf("%d\n", max(f[x][s], f[y - (1 << s) + 1][s]));
}
return 0;
}
End
本篇主要参考了 [ OI WIKI 的 ST表 讲解] 和 [洛谷模板题下 大佬们的题解]