对求解RMQ问题的ST算法的理解

源头:https://www.cnblogs.com/Milkor/p/4439468.html

说白了就是给定一个数组。指定任意合法区间内的最值问题。学习过线段树的话。自然而然地便可以知道可以利用线段树来解决。

而这里提供了另外一种更加强大(在时间复杂度上)的ST算法。(个人觉得这个算法是不能实时修改区间值的。)

让我们讨论该数组:

Array 4 2 3 5 6

下标   0 1 2 3 4 

有该事实

求区间[0,3]中的最小值为  [0,2]的最小值和[1,3]最小值中更小的值

也就是定义S(0,3) 为区间0~3的最小值

那么有S(0,3) = min (S(0,2),S(1,3)).也许你会觉得有重复的区间。但是这并不影响我们求最小值。

对于这个等式。我们可以知道。我们要求解S(0,3)。我们可以转化成求解S(0,2)以及S(1,3).

又或者是求解S(0,1),S(2,3) 如果说继续拆分下去。其实本质上就是线段树的做法了。

那假如我们提前处理好了S(0,n) 与 S(m,3)的值 其中m <= n+1.那我们可以O(1) 时间内获得S(0,3).(其实这是废话)。

另外我们这里用n,m来表示了。更加地广泛化。

那我们可以从这里思考我们需要预处理的区间。并且这些区间最好是能有一定的联系可以使得用类似动态规划的解法来快速地求得。而非一味地穷举。

并且我们要求解的区间可以拆分成2个我们处理过的区间 (其实也可以是多个我们处理过的区间。不过这个算法是2个。也许你可以自己创造算法。就好像归并排序你扩展地把一个数组分成3份进行操作一样,甚至n份,最后你会发现其实是一样的,而这里有点不同。因为我们不是递归到深处。而是直接做好预处理。)并且我们需要处理的区间越容易获得越好。

预处理:

F(i,j) 代表从i开始长度为2^j 的区间的极值

比如F(1,0) 便代表S(1,1)  (2^0)

F(1,2) 便代表S(1,2^2)

那么F(i,j) 便代表S(i,i+2^j-1)

而F(i,j)便是我们要初始化的区间。

首先关注F(i,j)的“容易处理”。

对于j的含义是2^j 你会想到什么?.也许我们可以建立状态转移。

F(i,j) = min (F(i,j-1),F(i+2^(j-1),j-1) 

细节理解可以从这句话中敲出:从1开始长度为4的区间。也就是说是从1开始长度为2的区间 以及从3开始长度为2的区间。

可以证明上述没错。

关注j变量。(为j的F状态是由为j-1的F状态得到的)上述式子便是动态方程。

我们先初始化F(i,0)。由j从小到大可以进行预处理了。

证明一下:

我们要求解的任意区间可以拆分成2个我们预处理的区间。确切地说不能是拆而是提取出。因为我们预处理过的区间必须覆盖我们求解区间 即使有重复部分也不要紧

其实就是:

S(i,j) = min(F(i,k),F(j-2^k+1,k))

也就是:

S(i,j) = min ( min(S(i,i+2^k-1)), min(S(j-2^k+1,j))) 

我们令 k = log2 (j-i+1).  表达成 k = In(j-i+1)/ In(2)  也就是令2^k 为j-i+1.

那么消去k.有S(i,j) = min ( min(S(i,j)), min(S(i,j)))。

也许你会觉得诧异。但是其实我们并不能做这步消去。因为令  k = log2 (j-i+1) 而这个值有可以导致k为小数。而我们预处理过的区间 k 可是整数!

那么我们在计算k的时候向下取整。之后又取2^k 这个操作可以使得 k 用二进制表达的话。就是去除掉最高位的1之外的所有1的值

比如9  1001  操作后 1000 为 8

     17 10001 操作后 10000 为16

而这种操作保留下来的值一定大于原来的值的一半。并且小于该值。

也就是做到了我们预处理的区间涵盖整个我们要求解的区间的条件了。very nice!

猜你喜欢

转载自blog.csdn.net/qq_35937273/article/details/82935418
今日推荐