例题引入:维护长度为 \(n\) 的序列 \(a\)\(q\) 个询问,查询区间最大值

1.暴力 \(n^2\)

由于 \(\max\) 没有像加法,乘法那样的可差分性,所以不能搞前缀和,只能 \(\Theta (n\times q)\) 硬跑,\(10^5\) 直接 T 到飞起

2.倍增优化(初始化)

其实有点像 DP 思想……我们设 \(s_{i,j}\) 为:
\(i\) 为起点长度为 \(2^j\) 的区间的最大值。
不难发现,\(s_{i.0}\) 就是 \(a_i\)
那么 \(s_{i,j}\) 呢?
我们把这个长度为 \(2^j\) 的区间劈成两半,每一半的长度都是 \(2^{j-1}\),那么左半边区间的起点就是 \(i\),右半边区间的起点就是 \(i+2^{j-1}\),整个区间的最大值就是左半边区间的最大值和右半边区间的最大值的最大值,即为:
$$s_{i,j}=max(s_{i,j-1},s_{i+2^{j-1},j-1})$$

我们要求遍历到 \(s_{i,j}\) 的时候,\(s_{i,j-1}\)\(s_{i+2^{j-1},j-1}\) 的值都已经确定好,不会再被更新了。
所以此时自然就需要先正序循环 \(j\),再正序(或者是倒序也行,没区别)循环 \(i\)
像这种循环次序的问题只要把背包彻底搞懂差不多就行了……

上代码:

for(int i=1;i<=n;i++) s_{i.0}=a[i];
for(int j=1;(1<<j)<=n;j++){
    for(int i=1;i+(1<<j)-1<=n;i++) s_{i,j}=max(s_{i,j-1},s[i+(1<<(j-1))][j-1]);
}

这里注意一下转移边界,不要让任何一个 \(s_{i,j}\) 所维护的区间超过了 \(n\) 或小于了 \(1\)
这一步复杂度是 \(\Theta(n\log n)\)

3.查询

每次查询输入一个 \(l,r\)
则我们设查询区间的长度 \(r-l+1\)\(len\)
要找一个最大的 \(D\),满足 \(D\)\(2\) 的整数次幂,并且 \(D\le len\)
显然 \(D>\lfloor\frac{len}{2}\rfloor\)
于是我们构造两个长度为 \(D\) 的区间,一个左端点为 \(l\),另外一个右端点为 \(r\)
因为 \(D>\lfloor\frac{len}{2}\rfloor\) ,所以这两个区间必能将 \([l,r]\) 完全覆盖。
这两个区间会有重叠,但是不用管,因为最大值可以重复计数。
然后找到这两个区间的最大值 \(m_1,m_2\), 答案即为 \(\max(m_1,m_2)\)

根据这个性质,我们可以发现,ST 表其实处理的就是可以将某些数重复计算的问题,即可重复贡献问题,比如 \(\min,\max,\gcd,\text{lcm}\) 等。而像加法,乘法等则不能用 ST 表,否则重复计算重叠部分之后答案会发生改变。

现在设 \(D=2^b\) ,根据上图,\(m_1\) 就等于 \(s_{l,b}\)\(m_2\) 就等于 \(s_{r-2^b+1,b}\)
不难发现 \(b=\lfloor \log_2{len}\rfloor\),预处理一下即可。
还是放一下代码吧。

int ask(int l,int r){
	int len=r-l+1;
	return max(f[l][b[len]],f[r-(1<<b[len])+1][b[len]]);
}