当前位置: 代码迷 >> 综合 >> ST表(Sparse Table)
  详细解决方案

ST表(Sparse Table)

热度:90   发布时间:2023-11-23 21:57:54.0

文章目录

  • 一、介绍
    • 1.作用——可重复贡献问题
    • 2.ST表的优劣
  • 二、实现(基于RMQ)
    • 1.预处理
    • 2.查询
    • 3.对数优化
    • 4.模板代码
  • 三、例题
    • 最大数


一、介绍

1.作用——可重复贡献问题

对于操作 o p t opt opt,满足 ? l ≤ k ≤ r , o p t [ l , r ] = o p t ( o p t [ l , k ] , o p t [ k , r ] ) \forall l \leq k \leq r,opt[l,r]=opt( opt[l,k],opt[k,r]) ?lkr,opt[l,r]=opt(opt[l,k],opt[k,r]),例如 R M Q RMQ RMQ区间求最值,又如区间 G C D GCD GCD,都可以称为可重复贡献问题。
而ST表就是用于解决以 R M Q RMQ RMQ问题为代表的可重复贡献问题。

2.ST表的优劣

之前我们学习过线段树,同样可以用于解决 R M Q RMQ RMQ问题,与线段树相比,稀疏表的码量更少,但同样,稀疏表所适合的情况也更局限一些。
稀疏表包含两个过程,分别是预处理和查询,预处理的时间复杂度为 O ( n l o g n ) O(n\ logn) O(n logn),而查询可以在 O ( 1 ) O(1) O(1)内实现。
稀疏表的局限在于不支持在线修改,一旦预处理结束后,就只能对静态的区间进行查询(一般情况)


二、实现(基于RMQ)

我们以 s t [ i ] [ j ] st[i][j] st[i][j]表示 m a x { a [ k ] ∣ i ≤ k ≤ i + 2 j ? 1 } max\{a[k]\mid i\leq k \leq i+2^j-1\} max{ a[k]iki+2j?1}

1.预处理

思路与动态规划一致,自左向右,自上往下递推出结果。
需要注意一下计算顺序!!!

递推式 s t [ i ] [ j ] = m a x ( s t [ i ] [ j ? 1 ] , s t [ i + ( 1 < < ( j ? 1 ) ) ] [ j ? 1 ] ) st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]) st[i][j]=max(st[i][j?1],st[i+(1<<(j?1))][j?1])
基于 ? l ≤ k ≤ r , o p t [ l , r ] = o p t ( o p t [ l , k ] , o p t [ k , r ] ) \forall l \leq k \leq r,opt[l,r]=opt( opt[l,k],opt[k,r]) ?lkr,opt[l,r]=opt(opt[l,k],opt[k,r])
在这里插入图片描述

for(int j=1;j<=logn;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]);}}

2.查询

若给定任意区间范围 [ l , r ] [l,r] [l,r],如何根据 s t [ i ] [ j ] st[i][j] st[i][j]求出对应结果?
a n s = m a x { s t [ l ] [ k ] , s t [ r ? ( 1 < < k ) + 1 ] [ k ] } ans=max\{st[l][k],st[r-(1<<k)+1][k]\} ans=max{ st[l][k],st[r?(1<<k)+1][k]}

s t [ l ] [ k ] st[l][k] st[l][k]表示区间 [ l , l + 2 k ? 1 ] [l,l+2^k-1] [l,l+2k?1]
s t [ r ? ( 1 < < k ) + 1 ] [ k ] st[r-(1<<k)+1][k] st[r?(1<<k)+1][k]表示区间 [ r ? 2 k + 1 , r ] [r-2^k+1,r] [r?2k+1,r]
为了保证区间的重叠,应满足 r ? 2 k + 1 ≤ l + 2 k ? 1 r-2^k+1 \leq l+2^k-1 r?2k+1l+2k?1
化为: r ? l 2 + 1 ≤ 2 k \frac{r-l}{2}+1\leq 2^k 2r?l?+12k
对式子两边取对数,
得: l o g 2 ( r ? l 2 + 1 ) ≤ k log_2(\frac{r-l}{2}+1)\leq k log2?(2r?l?+1)k

k k k直接取 l o g 2 ( r ? l 2 + 1 ) log_2(\frac{r-l}{2}+1) log2?(2r?l?+1),可以得到一个唯一的中间点 r + l 2 \frac{r+l}{2} 2r+l?,看上去这应该是理想的值,但实际上我们很难使得 k k k恰好等于 l o g 2 ( r ? l 2 + 1 ) log_2(\frac{r-l}{2}+1) log2?(2r?l?+1),首先是计算 r ? l 2 \frac{r-l}{2} 2r?l?要考虑结果会自动舍去小数部分,其次是计算对数也需要化为整型,依然会舍去小数部分,这些都会使得最终计算出来的 k k k小于 l o g 2 ( r ? l 2 + 1 ) log_2(\frac{r-l}{2}+1) log2?(2r?l?+1)

所以我们令 k k k等于 l o g 2 ( r ? l + 1 ) log_2(r-l+1) log2?(r?l+1),不必考虑除法计算,虽然计算过程中仍然要考虑对数化为整型的情况,但可以保证两个子区间可以覆盖大的区间。

3.对数优化

过程中需要多次使用log,本来可以直接使用 S T L STL STL中的函数,但为了避免超时,可以直接倍增打表。
l o g [ i ] = { ? 1 i = 0 l o g [ i 2 ] + 1 i > 0 log[i]=\begin{cases} -1 & i=0 \\ log[\frac{i}{2}]+1 & i>0\\ \end{cases} log[i]={ ?1log[2i?]+1?i=0i>0?

4.模板代码

模板题

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const int logn=log2(N);
int n,m;
int st[N][logn];
int Logn[N]; 
inline int read()
{
    int x=0,f=1;char ch=getchar();while (ch<'0'||ch>'9'){
    if (ch=='-') f=-1;ch=getchar();}while (ch>='0'&&ch<='9'){
    x=x*10+ch-48;ch=getchar();}return x*f;
}
int main(){
    n=read();m=read();Logn[0]=-1;for(int i=1;i<=n;i++){
    st[i][0]=read();Logn[i]=Logn[i/2]+1;//计算以2为底的对数 }for(int j=1;j<=Logn[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]);}}int l,r,k;while(m--){
    l=read();r=read();k=Logn[r-l+1];printf("%d\n",max(st[l][k],st[r-(1<<k)+1][k]));}
}

三、例题

最大数

题目链接
可以使用线段树,但也采用稀疏表的变形——逆稀疏表,每次更新只需对最后一行进行更新即可。
s t [ i ] [ j ] st[i][j] st[i][j]表示 m a x { a [ k ] ∣ i ? 2 j + 1 ≤ k ≤ i } max\{a[k]\mid i-2^j+1\leq k \leq i\} max{ a[k]i?2j+1ki}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5; 
const int logn=log2(N);
ll st[N][logn];
ll Logn[N];
ll cnt;
ll m,d,n;
char c;
void pre(){
    Logn[0]=-1;for(int i=1;i<N;i++){
    Logn[i]=Logn[i/2]+1;//计算以2为底的对数}
}ll query(ll L){
    int l=cnt-L;int r=cnt-1;int k=Logn[r-l+1];return max(st[r][k], st[l+(1<<k)-1][k]);
}void insert(ll val){
    st[cnt][0]=val;for (int i=1;cnt-(1<<i)+1>=0;i++){
    st[cnt][i]=max(st[cnt][i-1],st[cnt-(1<<(i-1))][i-1]);}cnt++;
}int main(){
    pre();cin>>m>>d;ll t=0;while (m--){
    cin>>c>>n;if (c=='A'){
    n=(n+t)%d;insert(n);}else if (c=='Q'){
    cout<<(t=query(n))<<endl;}}return 0;
}

  相关解决方案