ST 表学习笔记

· · 算法·理论

目录

  1. 概述

  2. 原理

  3. 实现

    3.1. 预处理

    3.2. 查询

  4. 拓展

    4.1 处理不可重复贡献问题

    4.2 末尾添加元素

  5. 参考文献

1.概述

ST 表,是一种能以 \mathcal{O}(n\log n) 预处理,\mathcal{O}(1) 查询的数据结构。

ST 表用于处理可重复贡献的问题{}^{[1]}。

## 2.原理 下面的部分都以 $\max$ 举例。 ST 表的实现是基于倍增和 dp。 设 $f_{i,j}$ 为 $[i,i + 2^j-1]$ 区间的 $\max$ 值。 换一种说法,$f_{i,j}$ 就是以 $a_i$ 开始,长度为 $2^j$ 的区间 $\max$ 值。 显然,$f_{i,0}=a_i$。 接下来,考虑状态转移方程。 ![](https://cdn.luogu.com.cn/upload/image_hosting/jgbbka60.png) 从上图可以看出,$f_{i,j}=\max(f_{i,j-1},f_{i+2^{j-1},j-1})$。 这个状态转移方程的意思,就是在上图中的红色左半部分和右半部分的最大值。 接下来就是查询了。 ![](https://cdn.luogu.com.cn/upload/image_hosting/e6fd3n9p.png) 我们可以把区间拆分成左边的红色区间和右边的蓝色区间,长度显然就是 $\log_{2}\text{区间长度}$。 因为 $\max$ 是可重复的问题,所以重复并不会对答案产生影响。 设区间左端点是 $l$,右端点是 $r$,$k=\lfloor\log_{2}(r-l+1)\rfloor$。 那么 $\max(a_l,a_{l+1},a_{l+2}\cdots a_r)=\max(f_{l,k},f_{r-2^k+1,k})$。 因为 $2^k\times2=2^{k+1}>n$,所以肯定能覆盖所有区间。 就这样,我们实现了 $\mathcal{O}(n\log n)$ 预处理,$\mathcal{O}(1)$ 询问的算法——ST 表。 ## 3.实现 ### 3.1.预处理 ```cpp for(int i = 1;i <= n;i++)f[i][0] = a[i];//边界条件 for(int j = 1;j <= log2(n);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]);//状态转移方程 } } ``` 但实际上,由于待会要用到很多次 `log`,可以用 $\log(i)=\log(\lfloor i\div2\rfloor)+1$ 的公式来预处理。 优化过后的代码: ```cpp for(int i = 1;i <= n;i++)f[i][0] = a[i],LOG[i] = LOG[i >> 1] + 1;//多了 log 的预处理 for(int j = 1;j <= LOG[n];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]); } } ``` ### 3.2.查询 这里只给出优化过后的代码。 ```cpp int ask(int l,int r){ int k = LOG[r - l + 1]; return max(st[l][k],st[r - (1 << k) + 1][k]); } ``` ## 4.拓展 这两条都是 @[rainygame](/user/804607) 教我的,快去膜拜 ta。 ### 4.1.处理不可重复贡献问题 如果你还不知道什么是“可重复贡献问题”,可以看一下前面的[概述](https://www.luogu.com.cn/blog/linxuanrui/st-biao-xue-xi-bi-ji#:~:text=1.概述),里面有提一嘴。 如果你有仔细看这篇博客的话,你可能会有疑问:你不是说过 ST 表只能处理可重复的贡献问题吗?为什么又可以处理不可重复贡献问题呢? 我们回忆一下 ST 表的原理。ST 表是基于倍增 + DP 实现的。 说到倍增,大家可以看一下 [OI-wiki 的介绍](https://oi-wiki.org/basic/binary-lifting)。 看完之后,我们很自然的可以想到:既然 ST 表已经处理出了长度是 $2^x$ 的区间运算,那么我们可以把询问的区间做二进制拆分。这样就可以做到 $\mathcal{O}(\log n)$ 询问了。 这里的代码用区间按位与来举例子。 ```cpp //q 次询问,求区间按位与。 #include<bits/stdc++.h> #define endl '\n' typedef long long ll; using namespace std; const int N = 1e5 + 5; int n,q,l,r,a[N],st[N][31],LOG[N]; int ask(int l,int r){ int k = LOG[r - l + 1],now = l,ans = INT_MAX; for(int i = k;i >= 0;i--){ if((1 << i) & (r - l + 1)){ if(ans == INT_MAX)ans = st[now][i]; else ans &= st[now][i]; now = now + (1 << i); } } return ans; } int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> q; for(int i = 1;i <= n;i++)cin >> a[i],st[i][0] = a[i],LOG[i] = LOG[i >> 1] + 1; for(int j = 1;j <= LOG[n];j++){ for(int i = 1;i + (1 << j) - 1 <= n;i++){ st[i][j] = (st[i][j - 1] & st[i + (1 << j - 1)][j - 1]); } } while(q--)cin >> l >> r,cout << ask(l,r) << endl; } ``` ### 4.2.末尾添加元素 这个比 [4.1. 处理不可重复贡献问题](https://www.luogu.com.cn/blog/linxuanrui/st-biao-xue-xi-bi-ji#:~:text=4.1.处理不可重复贡献问题)要简单一点。 考虑向末尾添加时,`st` 数组的变化。 ![](https://cdn.luogu.com.cn/upload/image_hosting/cg5ofrfl.png) 发现只有 $st_{n-2^j + 1,j}$($0\le j\le \lfloor\log_{}n\rfloor$)会发生改变。一个一个更新即可。时间复杂度为 $\mathcal{O}(\lfloor\log_{}n\rfloor)

代码:

//q 次询问,每次询问格式如下:
//1 x 在数组后面添加一个数
//2 l r 输出 a_l ~ a_r 的最大值
#include<bits/stdc++.h>
#define endl '\n'
typedef long long ll;
using namespace std;
const int N = 2e5 + 5;
int n,q,opt,x,l,r,a[N],st[N][31],LOG[N];
int ask(int l,int r){
    int k = LOG[r - l + 1];
    return max(st[l][k],st[r - (1 << k) + 1][k]);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> q;
    for(int i = 1;i <= n;i++)cin >> a[i],st[i][0] = a[i],LOG[i] = LOG[i >> 1] + 1;
    for(int j = 1;j <= LOG[n];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]);
        }
    }
    while(q--){
        cin >> opt;
        if(opt == 1){
            cin >> x;
            a[++n] = x;
            LOG[n] = LOG[n >> 1] + 1;
            st[n][0] = x;
            for(int i = 1;i <= LOG[n];i++)st[n - (1 << i) + 1][i] = max(st[n - (1 << i) + 1][i - 1],st[n - (1 << (i - 1)) + 1][i - 1]);
        }else{
            cin >> l >> r;
            cout << ask(l,r) << endl;
        }
    }
}

5.参考文献

  1. ST 表 - OI Wiki

  2. liangbob 巨佬的博客