区间最值询问是求给定区间最值的问题。若总区间为[1,N],通常是有多次查询,每次查询是不同的总区间的子区间。
简单的方法是对每个子区间遍历从而找到最值,时间复杂度是O(N),但是如果有多次的查询,效率就会很低。而解决这个问题的一个很好的在线算法便是ST(Sparse_Table)算法
ST算法在O(nlogn)的预处理以后可以实现O(1)的查询效率。也就是说我们把大量的区间的最值预先求出来,之后查询时就可以直接获取。那么重点首先就是预处理。
ST算法使用了动态规划的思想:先计算一些区间的最小值,然后把每个询问区间拆成若干个已经计算了最小值的区间,并统计这些区间的最小值的最小值,从而得到答案。
为了更有效更快的预处理,我们把每个预处理的区间长度定为2的非负整数次幂的,因此设定d[i][j]表示区间[i,i+2^j-1] 最小值(本文假设都是求最小值)。那么我们就能得到动规方程:
d[i][j] = min(d[i][j-1],d[i+2^j][j-1])(j>=1)
d[i][j-1] 表示 d[i][j] 前一半区间的最小值,d[i+2^j][j-1] 表示后一半区间的最小值。比如要求区间[1,8]的最值,那可以先求[1,4]与[5,8]的最值,[1,4]再分解为[1,2],[3,4],[5,8]再分解为[5,6],[7,8]。转换为动规过程即是:
分解下来是这样子的,但是预处理的计算过程是从下向上一步步执行的。
j的遍历范围是[1,log(N)],而i的遍历范围[0,N+1-2^j]。所以得到预处理的时间复杂度是O(nlgn).
而区间的初始值(f[i][0] =i)都是已知的,整体的ST算法预处理步骤代码如下:
for (int i = 0; i < N; ++i) {
f[i][0] = a[i];
}
for (int j = 1; (1 << j) <= N; ++j) {
for (int i = 0; i + (1 << j) - 1 < N; ++i) {
f[i][j] = min(f[i][j-1], f[i+(1 << (j-1))][j-1]);
}
}
预处理之后,每次查询都快速求解出答案。
若查询[a,b]之间的最小值,我们可以通过求 min(f[a,k],f[b-2^k+1,k]) 得出,其中K是满足 2^k<=b-a+1的最大值, 这样能够满足f[a,k]和f[b-2^k+1,k] 对应的区间覆盖整个[a,b]. 因此
k= trunc(log_2(b-a+1)) = trunc(ln(b-a+1)/ln(2)). 求值代码较简单,此处就不贴了。
ST巧妙的利用了二进制以及动态规划思想,区间最值的种种特性,降低了代码复杂度,其很好应用例子是求两个节点的最小公共父节点。