初见 | 数据结构 | ST表

前言

在说ST表之前,我们先需要知道ST表是什么个东西

按照 OI WIKI 上说的,ST表是解决可重复贡献问题的数据结构

至于可重复贡献问题的解释,下面我引用 OI WIKI 上的解释

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

上面提到的 RMQ 是常见的要有ST表解决的问题

关于 RMQ 自然就是:

好吧,其实是区间最值问题,看洛谷的板子题的题解来看,这里ST表应该是解决解决静态区间的 RMQ 问题,下面就根据板子题来简单说下ST表如何区间查询求最大值

例题

这里的例题是[洛谷P3865 ST表]

大体思路

ST表基于倍增的思想,有着 \(O(n\log n)\) 的预处理,同时它可以 \(O(1)\) 的回答每一个询问,但由于它只厨力静态区间,于是它没有办法做到区间修改

上面说到,最大值是可重复贡献问题,因此区间在查询的时候出现覆盖是不会对答案的正确性有影响的,根据这一性质,查阅资料手模可以发现,我们至多需要两个区间来覆盖我们所查询的区间

具体解决方案

设二元组 \(a(i,j)\) 表示区间 \([i,i+2^j-1]\) 的最大值,设这个序列为 \(k\)

根据这个定义,我们可以发现一个切入口,即 \(a(i,0)=k_i\)

同时,根据倍增的思想可以发现第二维相当于我们倍增的时候跳了 \(2^j-1\),因此我们可以写出状态转移方程

\[a(i,j) = \max (a(i,j-1),a(i+2^{j-1},j-1)) \]

然后对于每个查询,直接简单的把 \([l,r]\) 分成 \([l,l+2^x-1]\)\([r-(2^x-1),r]\) 两块,这里的 \(x = \left \lfloor \log_2 (r-l+1) \ \right \rfloor\)

后面那段区间的分法,建议自己手模一下理解,或者问一下大佬们,我问了 Dfkuaid 明白了,但是我说不出来(?)

Code

下面放上这个模板题的代码

#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define S signed
#define U unsigned
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
#define mst(a, b) memset(a, b, sizeof(a))
#define ON std::ios::sync_with_stdio(false)
using namespace std;
I void fr(LL & x)
{
    LL f = 1;
    char c = getchar();
    x = 0;
    while (c < '0' || c > '9') 
    {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') 
    {
        x = (x << 3) + (x << 1) + c - '0';
        c = getchar();
    }
    x *= f;
}
I void fw(LL x)
{
    static LL stak[35];
    LL top=0;
    do
    {
        stak[top++]=x%10;
        x/=10;
    }
    while(x);
    while(top) putchar(stak[--top]+'0');
    putchar('\n');
}
const int MXX=1e5+5;
LL a[MXX][21],n,m,l,r;
I LL Q(LL l,LL r)
{
    LL temp=log2(r-l+1);
    Heriko max(a[l][temp],a[r-(1<<temp)+1][temp]);
}
S main()
{
    fr(n),fr(m);
    for(R LL i=1;i<=n;i++) fr(a[i][0]);
    for(R LL _=1;_<=21;_++)
        for(R LL i=1;i+(1<<_)-1<=n;i++)
            a[i][_]=max(a[i][_-1],a[i+(1<<(_-1))][_-1]);
    for(R LL i=1;i<=m;i++) fr(l),fr(r),fw(Q(l,r));
    Heriko Deltana;
}

需要注意的是,我这里用的是 log2() 这个函数,其实是偷懒,应当预处理到数组里,具体方式可以参见下面 OI WIKI 的Code

//HD再次标明出处 :OI WIKI
#include <bits/stdc++.h>
using namespace std;
const int logn = 21;
const int maxn = 2000001;
int f[maxn][logn + 1], Logn[maxn + 1];
inline int read() {
  char c = getchar();
  int x = 0, f = 1;
  while (c < '0' || c > '9') {
    if (c == '-') f = -1;
    c = getchar();
  }
  while (c >= '0' && c <= '9') {
    x = x * 10 + c - '0';
    c = getchar();
  }
  return x * f;
}
void pre() {
  Logn[1] = 0;
  Logn[2] = 1;
  for (int i = 3; i < maxn; i++) {
    Logn[i] = Logn[i / 2] + 1;
  }
}
int main() {
  int n = read(), m = read();
  for (int i = 1; i <= n; i++) f[i][0] = read();
  pre();
  for (int j = 1; j <= logn; 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 = 1; i <= m; i++) {
    int x = read(), y = read();
    int s = Logn[y - x + 1];
    printf("%d\n", max(f[x][s], f[y - (1 << s) + 1][s]));
  }
  return 0;
}

End

本篇主要参考了 [ OI WIKI 的 ST表 讲解][洛谷模板题下 大佬们的题解]

posted @ 2021-05-26 10:10  HerikoDeltana  阅读(218)  评论(0编辑  收藏  举报