学习稀疏表: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+2i−1]中的最大值。
例如 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} 2j−1个元素中的最大值,以及在从 i + 2 j − 1 i+2^{j-1} i+2j−1开始的 2 j − 1 2^{j-1} 2j−1个元素中的最大值,中的最大的一个值。
即分成两半(因为总共有 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=high−low+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+l−2k,k]中的最大值即可。
l − 2 k l-2^k l−2k是因为总共有 l l l个元素,已经计算了 2 k 2^k 2k个元素,还差 l − 2 k l-2^k l−2k个元素,这时只需要将范围整体往后移动 l − 2 k l-2^k l−2k即可。
下面是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表,题目链接: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"; } }