[算法学习笔记] ST表

简述

ST表主要用于解决 静态RMQ问题。实际上,凡是具备 可重复贡献和结合律的问题,都可以用 ST表 来解决。

ST表 的优化方式和前缀和差分类似,采取预处理,每次可以做到 \(O(1)\) 时间复杂度的查询。因此,它适用于 有大量查询操作的问题。如果牵扯到大量修改需要使用线段树或者分块等其他算法。(这就是称 ST表 适用于 静态RMQ问题而不是动态 RMQ 问题的原因)

对于静态 RMQ 问题,ST表的时间复杂度比线段树更优,而且更加好写,不容易犯错。

因此,在解决 RMQ 问题时,我们需要根据题意来选择 ST表或者线段树。

实现原理

ST表 运用了倍增的思想,实现基于动态规划,具体地,定义 \(f_{i,j}\) 表示从 \(i\) 开始连续 \(2^j\) 个数中的最大值。

初始预处理,我们可以采用动态规划的方法, 把一个区间分成两部分,第一个部分为 \((i,j-1)\),第二个部分为 \((1>>(j-1),j-1)\)。原理如图所示。
image

(图片选自Pecco的文章,这里表示感谢。)

对于查询,查询区间 \((l,r)\) 中,我们将其分为两部分,分别为:

\((l,l+2^s-1),(r-2^s+1,r)\)

我们显然期望符合条件的 \(s\) 最大,考虑最优情况,当 \(l+2^s-1=r\) 时,移项:

\[2^s=r+1-l \]

即:

\[s=\log_{2}(r-l+1) \]

当然 \(s\) 必须是整数,我们需要向下取整,也就是:

\[s=\lfloor\log_{2}(r-l+1)\rfloor \]

这是左区间的 right,也就是 \(2\) 的整次幂部分,对于右区间,查找区间 \((r-(2^s)+1,r)\) 即可。

由于我们只是需求它们两个区间的最大值,这两个区间可以重复,也就是满足上面所提到的 可重复贡献,反之,对于求区间和问题不可以使用该方法。

小技巧:对于 \(log\) 的求解,C++ 自带的log2函数太慢了,我们可以通过递推预处理。

递推式非常显然:\(log_i=log_{i\div 2}+1\)

如图:

image
(图片选自Pecco的文章,这里表示感谢。)

可以证明这两个区间可以覆盖区间 \(l,r\)

具体地,对于每次查询,输出 \(max(f_{l,s},f_{r-(1>>s)+1,s})\) 即可。

模板题 & 参考代码

模板题评测地址:https://www.luogu.com.cn/problem/P3865

给定一个长度为 \(n\) 的数组 \(a\),共有 \(m\) 次询问,每次询问给定一个区间 \((l,r)\) ,求这个区间的最大值。

Analysis

发现本题没有修改操作,只是对数组初始值进行操作,是 静态RMQ 问题,考虑 ST表。

具体见代码:

参考代码
    struct ST
    {
        int f[N][30];
        int lg[N];
        void init()
        {
            for(int i=1;i<=n;i++) f[i][0] = a[i];
            for(int j=1;j<=25;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=2;i<=n;i++) lg[i] = lg[i/2] + 1;
        }
        int query(int l,int r)
        {
            int s = lg[r-l+1];
            return max(f[l][s],f[r-(1<<s)+1][s]);
        }
    }st;

为了不被卡,请不要用endl。

posted @ 2023-10-15 22:44  SXqwq  阅读(23)  评论(0编辑  收藏  举报