这次我们讨论一下有关区间中的值的问题。如果你只想看RMQ,请跳过下面这几段,在第一段代码的后面有详细的讲解。
在竞赛中,我们经常遇到最值问题。但是出题者往往给我们出一些这样的题目,让我们找到第K优解,而不是最优,比如K小生成树、K优背包等等。这篇文章主要介绍另一个“K问题“,区间第K大值。
区间第K大值的题意很明确,对于一个区间,找到其中第K大的一个数输出。这个问题可以用O(n2)的算法枚举,但是当区间很大的时候这种方法就会很费时。我们还可以将区间内的序列排序,直接输出a[k+l-1](l是区间左端点)即可。
我们知道,快排的原理是找到一个标准点,然后进行交换、分组,直到它的左边(以递增为例)都比它小,右边都比它大为止。但是结合这道题来说,每进行一遍分 组,第K大值就会确定的位于其中一组,或者就是那个标准点。然后我们只用将有第K大值的那个组再进行分组、查找(不是标准点),或者直接输出标准点(正好是标准点)。这样,我们就可以以少于1/2的操作找到我们想要的数。
对于多次询问,我们要保留下原始序列,以免之后再寻找时出现错误。
给出一道比较水的题,大家可以试一下~
【题目描述】(rqnoj350)给出一个长度为N的序列A1,A2,A3,...,AN,其中每项都是小于10^5的自然数。现在有M个询问,每个询问都是Ai...Aj中第k小的数等于多少。数据范围:在60%的数据中,1≤N≤1000,1≤M≤1000在100%的数据中,1≤N≤10000,1≤M≤2000【输入格式】第一行两个正整数N,M。第二行N个数,表示序列A1,A2,...,AN。紧着的M行,每行三个正整数i,j,k(k≤j-i+1),表示询问Ai...Aj中第k小的数等于多少。【输出格式】共输出M行,第i行输出第i个询问的答案。【样例输入】4 34 1 2 31 3 12 4 31 4 4【样例输出】134
分析就不用了,直接贴代码吧~
参考代码:
1 program knum; 2 var 3 a,t:array[1..3000000]of longint; 4 n,k,i,m,l,r:longint; 5 function sort(l,r,k:longint):longint; //改编的快排 6 var 7 i,j,x,y:longint; 8 begin 9 if l=r then exit(a[l]); //两指针可能重合。这条语句可以减少时间消耗10 i:=l;11 j:=r;12 x:=a[(i+j) shr 1];13 repeat14 while a[i]<x do inc(i);15 while a[j]>x do dec(j);16 if i<=j then17 begin18 y:=a[i];19 a[i]:=a[j];20 a[j]:=y;21 inc(i);22 dec(j);23 end;24 until i>j;25 if(k>=i)then exit(sort(i,r,k)); //①26 if(k<=j)then exit(sort(l,j,k)); 27 exit(x); //如果是标准点就直接输出28 end;29 begin30 readln(n,m);31 for i:=1 to n do32 read(t[i]);33 for i:=1 to m do34 begin35 a:=t; //保留原序列,对“镜子“序列进行操作36 readln(l,r,k);37 writeln(sort(l,r,l+k-1)); //②38 end39 end.40 P.S.① a)因为k∈[l,r],所以不用判断i是否小于r;41 b)由于i>j,这条语句和以下两条语句没有交集。42 ②一定是l+k-1,因为是区间的第K小值,所以寻找k肯定不对。
当然,对于区间最值问题(RMQ),这种方法也能解决。为什么还要有求区间最值(RMQ)的伟大的ST算法呢?
有句颇有哲理的话说得好,存在即合理。师傅告诉我,上面改装快排的时间效率是O(nm),所以当m很大时,这种方法就无法快速出解了。这时,强大的ST(Sparse Table)算法应运而生(为了剧情需要^_^)!ST算法可以在O(nlogn)的时间中构造一个强大的数组,然后只用O(1)的时间就能针对每次询问找到解。
这个强大的数组的名字叫做f[i,j](好俗…),表示从区间的第i位开始,长度为2i的区间的最大值(姑且以求区间最大值为例)。这句话听起来很玄乎,可是怎么构造这个数组呢?
我们先把f[i,0]赋成读入的第i个数(也就是以i为起点,长度为20=1的区间内的最值)。因为j表示长度为2j,所以我们可以把整个f[i,j]表示的区间划分[i,j-1]和[i+2(j-1),j-1]两个区间,然后调用f中存储的两个区间中的最大值,取其中较大的那个就行!
有的人可能疑惑,f[i,j]划分的两个空间的最值是什么时候求出来的呢?别忘了我们的初始化。有了j=0时的最值,j=1时的最值就很好求了。显然,j的最大值是trunc(log2n),这样,我们只要让j从1循环到trunc(log2n),把目的区间由小逐渐扩大,之后就能随意地调用原来的解啦!整个过程循环完以后,任何一个长度为2j的区间的最值就全部求出来了。
长度为2j区间的最值是有了,但是如果询问的区间的长度2j和2j+1之间呢?举一个例子。假设原序列是4 2 8 6 1 7 3,求3到7这个区间的最大值。
寻找的时候就只用寻找图中的两个区间的最大值即可,这样整个区间就都能够被覆盖,即使交集也不会影响结果。
另外一道比较水的题,大家也可以试试~
【题目描述】(tyvj p1038) 老管家是一个聪明能干的人。他为财主工作了整整10年,财主为了让自已账目更加清楚。要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,问题是这样的:在a到b号账中最少的一笔是多少?为了让管家没时间作假他总是一次问多个问题。【输入格式】输入中第一行有两个数m,n表示有m(m<=100000)笔账,n表示有n个问题,n<=100000。第二行为m个数,分别是账目的钱数后面n行分别是n个问题,每行有2个数字说明开始结束的账目编号。【输出格式】输出文件中为每个问题的答案。具体查看样例。【样例输入】10 31 2 3 4 5 6 7 8 9 102 73 91 10【样例输出】2 3 1
参考代码:
1 program ST; 2 var 3 f:array[0..100000,0..19]of longint; //j的最大值是trunc(log2n),计算器算一下 4 i,j,n,m,l,r:longint; 5 function min(x,y:longint):longint; 6 begin 7 if x<y then exit(x) 8 else exit(y); 9 end;10 begin11 readln(n,m);12 for i:=1 to n do //初始化f数组13 read(f[i,0]);14 for j:=1 to trunc(ln(n)/ln(2)) do //循环j的长度15 for i:=1 to n-1<<j+1 do //i的最大值n-1<<j+116 f[i,j]:=min(f[i,j-1],f[i+1<<(j-1),j-1]); //动态规划更新f数组,注意两个区间17 for i:=1 to m do18 begin19 readln(l,r);20 j:=trunc(ln(r-l+1)/ln(2)); //j就是图示中的k21 write(min(f[l,j],f[r-1<<j+1,j]),' '); //跟图中一样22 end;23 end.
终于写完了~累死了…有什么不对的地方和说得不明白的地方欢迎大家提出!我一定尽快改正!
(Saltless原创,转载请注明出处)
来源:https://www.cnblogs.com/saltless/archive/2010/08/11/1797170.html