RMQ
解释:
RMQ 问题是求给定区间中的最值问题,如下图所示:
RMQ 问题(图中记录的是最小值的位置)
当然,最简单的算法是 O(n)的,但是对于查询次数很多 m(假设有 100 万次),则这个算法的时
间复杂度为 O(mn),显然时间效率太低。可以用线段树将查询算法优化到 O(logn)(在线段树中保
存线段的最值) ,而线段树的预处理时间复杂度为 O(n),线段树整体复杂度为
ST表:
解释:
ST 算法,即 Sparse Table 算法。下面把 ST 算法分成预处理和查询两部分来说明(以求最小值
为例),它的时间复杂度为
1.1.1 预处理:lll
预处理使用 DP 的思想,f(i, j)表示[i, i+2^j 1]区间中的最小值,即 f[i,j]表示从第 i 个数起连
RMQ 和 LCA
2
续 2^j 个数中的最小值。我们可以开辟一个数组专门来保存 f(i, j)的值。
例如,f(1, 0)表示[1,1]之间的最小值,就是 num[1];f(1, 2)表示[1, 4]之间的最小值, f(2, 4)
表示[2, 17]之间的最小值。
注意, 因为 f(i, j)可以由 f(i, j 1)和 f(i+2^(j1), j1)导出, 而递推的初值(所有的 f(i, 0)
=num[i])都是已知的。所以我们可以采用自底向上的算法递推地给出所有符合条件的 f(i, j)的值。
ST 算法(图中记录的是最小值的位置)
ST 算法的状态转移方程:
1
[ ] , 0
( , )
min ( ( , 1), ( 2 , 1), 0
j
a i j f i j f i j f i j j -
Ï =
= Ì
Ó - + - >
例 如 : f(2,3) 保 存 的 是 a[2],a[3],a[4],……,a[9] 中 的 最 小 值 , 而
f(2,3)=min(f(2,2),f(6,2))=min((a[2],a[3],a[4],a[5]),(a[6],a[7],a[8],a[9]))
1.1.2 查询:
假设要查询从 m 到 n 这一段的最小值, 那么我们先求出一个最大的 k, 使得 k 满足
2 ( 1)
k £ n - m + ,于是我们就可以把[m, n]分成两个(部分重叠的)长度为 2
k
的区间:
[ , 2 1]
k
m m + - , [ 2 1, ]
k
n - + n ;
而 我 们 之 前 已 经 求 出 了 f (m, k ) 为 [ , 2 1]
k m m + - 的 最 小 值 , ( 2 1, )
k f n - + k 为
[ 2 1, ]
k
n - + n 的最小值。
我们只要返回其中更小的那个, 就是我们想要的答案, 这个算法的时间复杂度是 O(1)的。
RMQ 和 LCA
3
k= trunc(ln(rl+1)/ln(2)); // 求[l,r]之间的最小值
ans:=max(F[l,k],F[r2^k+1,k]);
例如, rmq(1,12) = min(f(1,3), f(5,3)) ( 2 k = log (12 -1+1) = 3 )
ST 算法的 O(1)查询(有部分重叠)
function max(x,y: longint): longint;
begin
max:=x;
if y>x then max:=y;
end;
function query(s,t: longint): longint; // 查询[s,t]间的最大值
var
k: longint;
begin
k:=trunc(ln(ts+1)/ln(2));
query:=max(a[s,k],a[t(1<<k)+1,t]); // 即max(a[s,k],a[t2^k+1,t])
end;
procedure init;
var
i,j,p: longint;
begin
assign(input,'rmqst.in');
reset(input);
readln(n);
for i:=1 to n do read(a[i,0]);
p:= trunc(ln(n)/ln(2));
for j:=1 to p do // a[i,j]表示从i开始,2^j个元素的最大值
for i:=1 to (n(1<<j)+1) do // n(1<<j)+1 即 n2^j+1
a[i,j]:=max(a[i,j1],a[i+1<<(j1),j1]);
close(input);
end;
1.2 巧妙易实现的分块 sqrt(n)算法: (★★★)
把数组分割成 sqrt(n)大小的段。用一个数组 M[1..sqrt(n)]为每一段保存最小值(或位置)。
M 可以很容易在 O(n)时间内完成预处理。
如在上例中,计算 RMQ(3,8)的值,我们应该比较 A[3]、M[2]、A[7]、A[8]值即可。可以很容
易看出这个算法每一次查询不会超过 3*sqrt(n)次操作。
这个方法非常容易实现,并且还可以将它改成问题的动态版本 .... (边查询、边改元素)。