稀疏表(ST表)

稀疏表(ST表)

此文介绍一种数据结构——ST表(Sparse Table),以及如何使用这个数据结构解决可重复贡献问题

可重复贡献问题

可重复贡献问题指的是,对于一种运算 o p t opt opt,满足 o p t ( x , x ) = x opt(x,x) = x opt(x,x)=x,并且 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]) o p t opt opt还必须满足结合律。之后给定任意区间 [ l , r ] [l,r] [l,r],求解 o p t   [ l , r ] opt \ [l,r] opt [l,r]的问题。

我们特化一下这个定义,例如,最大值有 max ⁡ ( x , x ) = x \max(x,x) = x max(x,x)=x ,gcd 有 gcd ⁡ ( x , x ) = x \gcd(x,x) = x gcd(x,x)=x ,所以 RMQ 和区间 GCD 就是一个可重复贡献问题。像区间和就不具有这个性质,如果求区间和的时候采用的预处理区间重叠了,则会导致重叠部分被计算两次,这是我们所不愿意看到的。另外, o p t opt opt还必须满足结合律才能使用 ST 表求解。

下文把RMQ问题作为例子讲解ST表。

ST表构建

我们定义 M a x ( i , e ) Max(i,e) Max(i,e)为区间 [ i , i + 2 e − 1 ] [i,i+2^{e} - 1] [i,i+2e1]中的最大值,这个区间的长度是 2 e 2^{e} 2e,特别的 M a x ( i , 0 ) = a i Max(i,0) = a_{i} Max(i,0)=ai,其中 a i a_{i} ai表示数组中的第 i i i个元素(数组下标从1开始)。

之后,我们使用倍增的思想,对这个 M a x ( i , e ) Max(i,e) Max(i,e)数组进行倍增。即下式:

M a x ( i , e ) = max ⁡ ( M a x ( i , e − 1 ) , M a x ( i + 2 e − 1 , e − 1 ) ) Max(i,e) = \max(Max(i,e-1),Max(i + 2^{e-1},e-1)) Max(i,e)=max(Max(i,e1),Max(i+2e1,e1))

倍增
其中时间复杂度为 Θ ( n log ⁡ n ) \Theta(n \log n) Θ(nlogn)

ST表查询

对于ST表的查询也很简单,只需要计算出查询区间的长度对应的 e e e,然后左端点查询一下,右端点查询一下即可,以保证覆盖全部的区间。

M a x ( l , r ) = max ⁡ ( M a x ( l , k ) , M a x ( r − 2 k + 1 , k ) ) k = ⌊ log ⁡ 2 ( r − l + 1 ) ⌋ Max(l,r) = \max(Max(l,k),Max(r - 2^{k} + 1,k)) \\ k = \left \lfloor \log_{2}(r - l + 1) \right \rfloor Max(l,r)=max(Max(l,k),Max(r2k+1,k))k=log2(rl+1)

查询
其中时间复杂度为 Θ ( 1 ) \Theta(1) Θ(1)

对数优化

在计算对数 log ⁡ 2 n \log_{2}n log2n的时候,我们可以利用倍增的思想构造对数表,即:

log ⁡ 2 1 = 0 … log ⁡ 2 n = log ⁡ 2 ⌊ n 2 ⌋ + 1 \log_{2}1 = 0 \\ \ldots \\ \log_{2}n = \log_{2}\left \lfloor \frac{n}{2} \right \rfloor + 1 log21=0log2n=log22n+1

例题

P3865【模板】ST表

#include <bits/stdc++.h>

using namespace std;

int MAX[1000010][18];
int LOG[1000010];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}

void buildST(int len)
{
    for (int e = 1; e < 18; e++)
    {
        for (int i = 1; i + (1 << e) - 1 <= len; i++)
        {
            MAX[i][e] = max(MAX[i][e - 1], MAX[i + (1 << (e - 1))][e - 1]);
        }
    }
    LOG[1] = 0;
    for (int i = 2; i <= len; i++)
    {
        LOG[i] = LOG[i / 2] + 1;
    }
}

int queryST(int l, int r)
{
    int e = LOG[r - l + 1];
    return max(MAX[l][e], MAX[r - (1 << e) + 1][e]);
}

int main()
{
    int len = read(), m = read();
    for (int i = 1; i <= len; i++)
        MAX[i][0] = read();
    buildST(len);
    while (m--)
    {
        int l = read(), r = read();
        printf("%d\n", queryST(l, r));
    }

    return 0;
}

逆向ST表

逆向ST表的思路是我们定义 M a x ( i , e ) Max(i,e) Max(i,e)为区间 [ i − 2 e + 1 , i ] [i-2^{e} + 1,i] [i2e+1,i]中的最大值正好对应 i i i的逆向。因此,在这种情况下,在ST表的尾部插入一个节点是简单的,因为插入的这个节点不会影响前面节点的建立,并且只需要更新关于新节点的数组即可。

P1198

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define FR freopen("in.txt", "r", stdin)

ll st[200005][18];

int LOG[200005];
int idx = 0;

void initLOG()
{
    LOG[1] = 0;
    for (int i = 2; i < 200005; i++)
    {
        LOG[i] = LOG[i / 2] + 1;
    }
}

ll query(int L)
{
    int sta = idx - L;
    int ed = idx - 1;
    int e = LOG[ed - sta + 1];

    return max(st[ed][e], st[sta + (1 << e) - 1][e]);
}

void insert(ll val)
{
    st[idx][0] = val;
    for (int e = 1; idx - (1 << e) + 1 >= 0; e++)
    {
        st[idx][e] = max(st[idx][e - 1], st[idx - (1 << (e - 1))][e - 1]);
    }
    idx++;
}

int main()
{
    initLOG();

    int m;
    ll p;
    cin >> m >> p;
    ll t = 0;
    while (m--)
    {
        char op;
        ll n;
        cin >> op >> n;
        if (op == 'A')
        {
            n = (n + t) % p;
            insert(n);
        }
        else if (op == 'Q')
        {
            cout << (t = query(n)) << endl;
        }
    }

    return 0;
}

关于GCD的ST表

CF1548B

此题应考虑差分,如果一个连续的子序列有相同的余数,那么他们的差分都应该是商的倍数,即连续的差分数组的GCD大于1。

求一个连续区间的GCD应考虑ST表,然后再加上尺取即可。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值