ST 表 (Sparse Table):对于可重复贡献问题,可在 O(nlogn) 完成初始化,在 O(1) 回答每个区间查询的数据结构。但是不支持修改数据。

可重复贡献问题:对于运算 ,如果 xx=x,且 要满足结合律,则对应的区间询问是可重复贡献问题。符合这个性质的常见运算有 max,min,gcd.

ST 表

预处理

对于一个数列,ST 表储存的便是其二的幂次长度的每一段的计算结果。

具体点来说,如果一个数列长度为 9,我们用中括号 [l,r] 表示要查询的区间,那么 ST 表储存的便是:

  • 长度为 1 的结果:[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9]
  • 长度为 2 的结果:[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8],[8,9]
  • 长度为 4 的结果:[1,4],[2,5],[3,6],[4,7],[5,8],[6,9]
  • 长度为 8 的结果:[1,8],[2,9]

将区间可视化的话如下图:

对于长度为 n 的数列,将要计算 logn 个不同长度的结果,每种长度结果的数量级为 n. 那么预处理的时间复杂度为:O(nlogn)

预处理数据储存在一个二维数组 f 内,第一维为区间长度,第二维为区间起始下标:

  • 首先读入数列,储存到长度为 1 的结果内
  • 然后通过上一层的结果递推本层结果。即:对于 2k 长度的结果,使用 2k1 的结果进行计算。转移方程为( 代表具体场景的运算):fi,j=(fi1,j,fi1,j+2i1)

求解

由于 ST 表解决的是可重复贡献问题,那么两个有重复部分的区间的进行计算的话,并不会影响答案的正确性。

具体点来说,如果我们要求一个数列的区间 [2,8] 的最大值,那么我们对 [2,6] 的最大值和 [4,8] 的最大值取最大值,就能得到 [2,8] 的最大值。

对于任意一个区间 [l,r],我们都可以分解为上面预处理出来的两个区间,具体分解方式如下:

  • 记区间长度为 d=rl+1
  • 分解出来的两个区间的长度为 d=2log2d (相当于找到 d 的最大的 2 的幂次)
  • 分解出来的区间为:[l,l+(d1)][r(d1),r]

将区间分解好后,我们就用上面预处理得到的结果直接计算出答案。

因此可以在 O(1) 取得任意一个区间的结果。

模板

运算为 max,数列长度为 N.

预处理

void init()
{
    for (int i = 1; i <= N; i++)
        cin >> f[0][i];
    for (int i = 1; i <= LOGN; i++)
        for (int j = 1; j <= N - (1 << i) + 1; j++)
            f[i][j] = max(f[i - 1][j], f[i - 1][j + (1 << (i - 1))]);
}

查询

int query(int l, int r)
{
    int len = r - l + 1;
    int pow = 0, pow2 = 1;
    while (pow2 * 2 <= len)
    {
        pow++;
        pow2 *= 2;
    }
    return max(f[pow][l], f[pow][r - pow2 + 1]);
}

此处每次查询都计算了一遍 2 的幂次,实际可以先预处理计算出来,不需要每次都算一遍。