稀疏表

稀疏表

学习稀疏表:https://www.youtube.com/watch?v=c5O7E_PDO4U

稀疏表(Sparse Table),也叫ST表,用来解决范围最值查询(Range Minimum Query)问题。

假设有一个数组,我们需要对该数组执行如下操作(以下全部假设数组下标从0开始):

  • 求一个区间 [ l o w , h i g h ] [low,high] [low,high]内的最大值

不同的解决方法所花的时间不同:

解决方法预处理时间查询时间
普通数组 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)
动态规划 O ( n 2 ) O(n^{2}) O(n2) O ( 1 ) O(1) O(1)
线段树 O ( n ) O(n) O(n) O ( log ⁡ 2 n ) O(\log_{2}n) O(log2n)
稀疏表 O ( n log ⁡ 2 n ) O(n\log_{2}n) O(nlog2n) O ( 1 ) O(1) O(1)

因此,稀疏表在上面4种方式中是最好的。

构造稀疏表

假设数组的大小为 N N N

首先需要一个二维数组 F [ N ] [ ⌊ log ⁡ 2 N ⌋ + 1 ] F[N][\lfloor{\log_{2}N}\rfloor+1] F[N][log2N+1],其中 F [ i ] [ j ] F[i][j] F[i][j]表示范围 [ i , i + 2 i − 1 ] [i, i+2^{i}-1] [i,i+2i1]中的最大值。

例如 F [ 1 ] [ 2 ] F[1][2] F[1][2]表示范围 [ 1 , 4 ] [1, 4] [1,4]中的最大值,即 1 1 1表示从 1 1 1开始, 2 2 2表示 2 2 = 4 2^{2}=4 22=4个元素,即下标从1开始的4个元素中的最大值。

构造稀疏表的过程可在 O ( n log ⁡ 2 n ) O(n\log_{2}n) O(nlog2n)时间内完成,思想类似于动态规划。下面是C++的简单实现:

#include <algorithm>

int n;
int arr[100000];
int sparse_table[100000][17];

void construct_sparse_table() {
    for (int i = 0; i != n; i++) {
        sparse_table[i][0] = arr[i];
    }

    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 0; i + (1 << j) - 1 < n; i++) {
            sparse_table[i][j] = std::max(sparse_table[i][j - 1], sparse_table[i + (1 << (j - 1))][j - 1]);
        }
    }
}

循环中的sparse_table[i][j]可以理解为从 i i i开始的 2 j 2^j 2j个元素的最大值。

它等价于在从 i i i开始的 2 j − 1 2^{j-1} 2j1个元素中的最大值,以及在从 i + 2 j − 1 i+2^{j-1} i+2j1开始的 2 j − 1 2^{j-1} 2j1个元素中的最大值,中的最大的一个值。

即分成两半(因为总共有 2 j 2^j 2j个元素),然后根据这两半的最大值计算最终的最大值。

查询范围最大值

假设范围为 [ l o w , h i g h ] [low, high] [low,high]

l = h i g h − l o w + 1 l=high-low+1 l=highlow+1(即范围内的元素个数), k = ⌊ log ⁡ 2 l ⌋ k=\lfloor\log_{2}l\rfloor k=log2l

因此只需要找到范围 [ l o w , k ] [low,k] [low,k] [ l o w + l − 2 k , k ] [low+l-2^k,k] [low+l2k,k]中的最大值即可。

l − 2 k l-2^k l2k是因为总共有 l l l个元素,已经计算了 2 k 2^k 2k个元素,还差 l − 2 k l-2^k l2k个元素,这时只需要将范围整体往后移动 l − 2 k l-2^k l2k即可。

下面是C++的简单实现:

#include <algorithm>
#include <cmath>

int n;
int arr[100000];
int sparse_table[100000][17];

int range_maximum_query(int low, int high) {
    int l = high - low + 1;
    int k = static_cast<int>(std::floor(std::log2(l)));

    return std::max(sparse_table[low][k], sparse_table[low + l - (1 << k)][k]);
}

洛谷P3865 ST表

利用这个数据结构,可以解决洛谷P3865 ST表,题目链接:https://www.luogu.com.cn/problem/P3865

下面是C++的代码:

#include <algorithm>
#include <cmath>
#include <iostream>

int n, m;
int arr[100000];
int sparse_table[100000][17];

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 construct_sparse_table() {
    for (int i = 0; i != n; i++) {
        sparse_table[i][0] = arr[i];
    }

    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 0; i + (1 << j) - 1 < n; i++) {
            sparse_table[i][j] = std::max(sparse_table[i][j - 1], sparse_table[i + (1 << (j - 1))][j - 1]);
        }
    }
}

int range_maximum_query(int low, int high) {
    int l = high - low + 1;
    int k = static_cast<int>(std::floor(std::log2(l)));

    return std::max(sparse_table[low][k], sparse_table[low + l - (1 << k)][k]);
}

int main() {
    std::cin >> n >> m;

    for (int i = 0; i != n; i++) {
        arr[i] = read();
    }

    construct_sparse_table();

    int l, r;
    for (int i = 0; i != m; i++) {
        l = read();
        r = read();

        std::cout << range_maximum_query(l - 1, r - 1) << "\n";
    }
}
相关推荐
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页