学习笔记:ST 表

ST 表

ST 表是用于解决 可重复贡献问题 的数据结构。

ST 表可以做到 $O(n\log n)$ 预处理,$O(1)$ 回答每个询问。但不支持修改操作。

基于倍增思想,我们考虑如何求出区间最大值。可以发现,如果按照一般的倍增流程,每次跳 $2^i$ 步的话,询问时的复杂度仍旧是 $O(\log n)$,并没有比线段树更优,反而预处理一步还比线段树慢。

然而,我们发现显然有 $\max(x,x)=x$,也就是说,区间最大值是一个具有「可重复贡献」性质的问题。即使用来求解的预处理区间有重叠部分,只要这些区间的并是所求的区间,最终计算出的答案就是正确的。

如果手动模拟一下,可以发现我们能使用至多两个预处理过的区间来覆盖询问区间,也就是说询问时的时间复杂度可以被降至 \Theta(1),在处理有大量询问的题目时十分有效。

具体实现如下:

令 $f_{i,j}$ 表示区间 $[i,i+2^j-1]$ 的最大值。

显然有 $f_{i,0}=a_i$。

根据定义式,第二维就相当于倍增的时候「跳了 $2^j-1$ 步」,依据倍增的思路,写出状态转移方程:$f_{i,j}=\max(f_{i,j-1},f_{i+2^{j-1},j-1})$

img

以上就是预处理部分。而对于查询,可以简单实现如下:

对于每个询问 $[l,r]$,我们把它分成两部分:$[l,l+2^s-1]$ 与 $[r-2^s+1,r]$,其中 $s=\left\lfloor\log_2(r-l+1)\right\rfloor$ 两部分的结果的最大值就是回答。

ST 表的查询过程

根据上面对于「可重复贡献问题」的论证,由于最大值是「可重复贡献问题」,重叠并不会对区间最大值产生影响。又因为这两个区间完全覆盖了 $[l,r]$,可以保证答案的正确性。

#include <iostream>
#define MAXN 100005
using namespace std;
int n, m, x, y, op;
int lg[MAXN];
int st[MAXN][25];
int read(){
    int t = 1, x = 0;char ch = getchar();
    while(!isdigit(ch)){if(ch == '-')t = -1;ch = getchar();}
    while(isdigit(ch)){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * t;
}
void write(int x){
    if(x < 0){putchar('-');x = -x;}
    if(x >= 10)write(x / 10);
    putchar(x % 10 + '0');
}
int main(){
    n = read();m = read();
    for(int i = 1 ; i <= n ; i ++)st[i][0] = read();
    for(int i = 2 ; i <= n ; i ++)lg[i] = lg[i >> 1] + 1;
    for(int i = 1 ; i <= lg[n] ; i ++)
        for(int j = 1 ; j <= n - (1 << i) + 1 ; j ++)
            st[j][i] = max(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
    for(int i = 1 ; i <= m ; i ++){
        x = read();y = read();op = lg[y - x + 1];
        write(max(st[x][op], st[y - (1 << op) + 1][op]));putchar('\n');
    }
    return 0;
}

注意

  1. 输入输出数据一般很多,建议开启输入输出优化。
  2. 每次重新计算 $\log$ 函数值并不值得,建议进行如下的预处理:

    $$ \lg_i=\begin{cases} 0, & i=1 \\ \lfloor\frac{i}{2}\rfloor+1, & otherwise \\ \end{cases} $$

posted @ 2023-09-18 15:39  tsqtsqtsq  阅读(23)  评论(0)    收藏  举报  来源