RMQ入门

Posted mojibake

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RMQ入门相关的知识,希望对你有一定的参考价值。

注:为方便描述算法 便于记忆 所以代码用Pascal书写 见谅

RMQ,即Range Minimum/Maximum Query问题,给定一个区间,询问不同子区间的最值问题。

当询问次数较少时,朴素算法的时间尚可(暴力做法),k次询问,最坏情况是每次询问最大区间,时间复杂度O(kL),其中k表示询问次数,L表示给定的区间长度。

随着询问次数的增加,朴素算法(应该可以认为是n2 级别的算法)就显得太慢了,因此可以很方便想出线段树做法,节点存储区间最值。那么此时k次查询,L的区间长度,可知时间复杂度为O(k logL);L为定值,则log L为常数,随着k的增大线性增大,相比较朴素算法是大大优化了。

但是还不够。当 n过于大时,即使是线段树也不行。此时考虑ST(Sparse Table)算法。该算法的时间复杂度为两部分:预处理O(L logL),查询则为O(1)。

(关于构造笛卡尔树的方法日后另写qwq)


ST方法的原理类似DP。比如说求[1,100]的最值,如果我能知道[1,64]的最值以及[37,100]的最值,那么总区间的最值,一定是两个分区间中的一个最值。

这个例子很方便理解ST。对于分区间继续拆分,直到出现长度为2的区间;此时长度为2的区间最值就是原序列中两个数的较大值。

运用倍增就可以完成拆分。

于是开始搭建:

 1 begin
 2     init;
 3     readln(m);
 4     for i:=1 to m do
 5         begin
 6             readln(head,tail);
 7             if head>tail then
 8                 begin
 9                     temp:=head;
10                     head:=tail;
11                     tail:=temp;
12                 end;
13             writeln(query(head,tail));
14         end;
15 end.

init即预处理过程,m为询问的组数。不断读入[head,tail]的待查询区间,给出查询值。


预处理就可以写出来:

 1 procedure init;
 2     var
 3         i:longint;
 4     begin
 5         read(n);
 6         for i:=1 to n do
 7             read(a[i]);
 8         tableLength:=trunc(ln(n)/ln(2));
 9         createTable(tableLength);
10         make;
11     end;

行5-行7就是总区间的读入。之后要打一个2的幂表用以拆分(运用倍增),打出了这个表才可以分区间——例子中的64就是这么算出来的。显然这个表长(也就是最大次数)=logL,写在代码中用换底公式后向下取整,得到表长。

之后以表长建立2的幂表,完成后进入预处理的核心部分make。

建表的code比较简单,不多加赘述。

1 procedure createTable(k:longint);
2     var
3         i:longint;
4     begin
5         powerTable[0]:=1;
6         powerTable[1]:=2;
7         for i:=2 to k do
8             powerTable[i]:=2*powerTable[i-1];
9     end;

预处理核心代码如下:

 1 procedure make;
 2     var
 3         i,j:longint;
 4     begin
 5         for i:=1 to n do
 6             line[i,0]:=a[i];
 7         for i:=1 to tableLength do
 8             for j:=1 to n-powerTable[i]+1 do
 9                 line[j,i]:=max(line[j,i-1],line[j+powerTable[i-1],i-1]);
10     end;

思想是DP的思想。对于总区间,先两端两端的划分,求其最值;再四段四段分,再八段八段分……line[i,j]表示序列中以i为起点,2的j次为长度的区间的最值。当j==0时自然就退化为当前位置的值。

要注意的是外层循环一定要是次数而不是起点,关于这点可以手动模拟一下感受感受;当外层循环为起点时,line数组求值中的依赖的有半段还未求出,故出错。


查询就比较简单了,由于预处理已经算出了所以可能用到的区间最值,只要做一次拆分,取两分区的max即可。

1 function query(left,right:longint):longint;
2     var
3         t:longint;
4     begin
5         t:=trunc(ln(right-left+1)/ln(2));
6         exit(max(line[left,t],line[right-powerTable[t]+1,t]));
7     end;

完整代码如下(其实只是多了点定义和一个max函数):

 1 var
 2     a:array[0..100005]of longint;
 3     i,n,m,head,tail,tableLength,temp:longint;
 4     line:array[0..100005,0..17]of longint;
 5     powerTable:array[0..17]of longint;
 6  
 7 function max(a,b:longint):longint;
 8     begin
 9         if a>b then
10             exit(a)
11         else
12             exit(b);
13     end;
14  
15 procedure createTable(k:longint);
16     var
17         i:longint;
18     begin
19         powerTable[0]:=1;
20         powerTable[1]:=2;
21         for i:=2 to k do
22             powerTable[i]:=2*powerTable[i-1];
23     end;
24  
25 procedure make;
26     var
27         i,j:longint;
28     begin
29         for i:=1 to n do
30             line[i,0]:=a[i];
31         for i:=1 to tableLength do
32             for j:=1 to n-powerTable[i]+1 do
33                 line[j,i]:=max(line[j,i-1],line[j+powerTable[i-1],i-1]);
34     end;
35  
36 procedure init;
37     var
38         i:longint;
39     begin
40         read(n);
41         for i:=1 to n do
42             read(a[i]);
43         tableLength:=trunc(ln(n)/ln(2));
44         createTable(tableLength);
45         make;
46     end;
47  
48 function query(left,right:longint):longint;
49     var
50         t:longint;
51     begin
52         t:=trunc(ln(right-left+1)/ln(2));
53         exit(max(line[left,t],line[right-powerTable[t]+1,t]));
54     end;
55  
56 begin
57     init;
58     readln(m);
59     for i:=1 to m do
60         begin
61             readln(head,tail);
62             if head>tail then
63                 begin
64                     temp:=head;
65                     head:=tail;
66                     tail:=temp;
67                 end;
68             writeln(query(head,tail));
69         end;
70 end.

行32的循环上界要控制好,否则会段出错,切记切记。

 

 

 

请使用手机"扫一扫"x

以上是关于RMQ入门的主要内容,如果未能解决你的问题,请参考以下文章

RMQ 算法入门

ACM入门之ST表/RMQ

推荐net开发cad入门阅读代码片段

Atom编辑器入门到精通 Atom使用进阶

Cg入门20:Fragment shader - 片段级模型动态变色(实现汽车动态换漆)

Cg入门19:Fragment shader - 片段级模型动态变色