RMQ倍增,附赠有趣小故事一发

RMQ-range minimum query

RMQ是一种在线的算法,用来求一个给定区间的最小值,其中预处理的时间复杂度是O(nlogn),而处理每次询问的时间复杂度是O(1)。所以,在问题特别多的时候,RMQ是处理大量询问的不二之选。
对于这个算法的实现,需要用到倍增和st表(线段树)的思想。
emm,话不多说,先讲小故事吧:
在一个遥远的地方,有一片广袤的大森林,这个森林中生活着许许多多的小动物,它们组成了许许多多的动物王国,有一天,兔子国国王老了,他意识到自己大限将至,急需找出下一任继承人,于是举行了一场兔子跳比赛,胜者将获得兔子国皇冠一枚。
在这里插入图片描述
比赛内容是这样的,所有的兔子都从下表为0的数组开始跳,谁先跳到指定区间中的最大元素a[x]谁就胜利,由于指定区间很多,所以a[x]的量非常巨大,所以这个比赛已经让很多的兔子被难倒在了路上。现在有三只兔子进入了决赛。
它们分别是:

  1. 2b小兔子,每次随机往前或者是往后跳rand()的距离。
    2.普通小兔子,一一步一个脚印,挨个看完区间内的所有元素,最后再跳到那个最大的元素上。
    3.超级小兔子,它就像是开了外挂一样,每次总是能精准地跳到最大值a[x]上,在2b小白兔还在前后前后嘿咻的时候,在普通小兔子还在头顶烈日一步一步个脚印的时候,超级小兔子早已完成了目标,进机房吹空调去了~。
    比赛结束后,超级小兔子当上了国王,迎娶兔子公主,走上人生巅峰,但没有gps没有北斗导航的它究竟是怎么做到的呢?
    没错,超级小白兔作弊了,每到夜黑风高的夜晚,它就会偷偷溜出家门,拿出它的小抄本,记录下每一个可能区间的最值,到了白天比赛的时候,它就偷偷摸出小抄本,在众目睽睽之下开始了它的表演,由于兔子国人均智力低下,所以没有人(兔子)知道小抄是什么东西。但是兔子国王总觉得事有蹊跷,它又把三只兔子召回来,重新进行比赛,但是这次的比赛,路途格外遥远,有成千上万个点需要检索,但是超级兔子并不畏惧,在这个夜黑风高的夜晚,它又带着小抄本出去了。但是马上它就发现了一个绝望的事实——小抄本不够用了!因为十个点就会有1010/2=50个可能区间,十万个点就会有100000100000/2=5000000000(50亿)个可能区间。
    而超级小兔子的小抄本只能装下五千万个区间最值,它绝望了,仰天长啸,涕泗横流,最终哭死在了路上,横尸荒野……

超级小兔子由于是哭死的,所以是个冤魂,被黑白无常带到阎王爷那里后阎王爷拒绝收留它,还被赶出了阴间。于是无事可做的小兔子就在地上闲逛,它逛呀逛,逛了很久,终于发现了2b小兔子,正在那里打着鼾睡觉。就在这时,天使出现了,她来到了超级小白兔的冤魂旁边,说:“真是抱歉啊,超级小兔子,我主人的工作出现了纰漏,不小心收了你的小命,让你成为一个冤魂,这样吧,为了稍稍补偿你一下,也为了表达我们的歉意,我代表我的主任同意并且帮助你附身在你们族里面智商最低的同类–2b小白兔上,继续作为一只活动兔子生存下去,你愿意吗?”超级小白兔瞬间乐开了话连忙点头同意。
按照超级小白兔的意愿,天使召开了附身仪式,刹那间一道彩虹色的光芒从天而降,待到光芒消散之时,躺在床上的已经不是2b小白兔,而是超级2b小白兔了(以下简称超2兔)。待到超2兔回过神来的时候,天已经蒙蒙亮了。因为今天还有最后的决赛,超2兔不敢耽搁,连忙从床上爬起来,向赛场赶去。
比赛开始了,但接下来的情况让超2兔大失所望,由于附身在2b小白兔身上,它的动作还是受2的控制,虽然跳的步数不是随机数了,但每次只能跳跃上一次步数了两倍,例如这次跳了一步,下次就只能挑两部,而往回跳却不受限制。超级小白兔又一次绝望了,但是它这一次没有哭死,而是蹲在地上思考人生,但是塞翁失马焉知非福,在地上思考人生的2b兔子终于领悟了,它又跳回了原点,并且掏出了它的小抄。
但是这一次,它并没有记录下所有能区间的最值,而是开了一个名字叫dp的数组,其中i代表出发点为i,而j代表区间i到2^j 中的最值,它将j作为外层循环,每次往前只跳一样的距离,比如上次是从1开始往后跳2^2-1=3到格子4并求出[1,4]中的最大值为 max(dp[1,2],dp[3,4]),又回到2,开始往后跳4-1=3格求max[2,5]=max(dp[2][3],dp[4][5] )以此类推……。直到更新完了所有的点,超2兔的小抄就完成了,这一次的小抄,由于并没有记录所有的可能区间,所以条目数量大大减少(nlog2n)并且由于条目减少,预处理的时间也变得更少了,这样一来,超2兔又变得如有神助,再次在超级小白兔缺赛,普通小白兔慢慢跳的时候,率先完成了任务目标,进机房吹空调了~。

那么问题来了,超级小白兔并没有做出所有区间的小抄,它是怎么再一次做到如有神助的呢?
答案就来自2b兔留给它的遗产上面,虽然并没有明确记录所有区间的最大值,但是超2小白兔的小抄,早已看穿了一切。由于是区间最大值,所以它具有传递性。


假如我们需要查询的区间为(i,j),那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂
(可以重复,比如查询5,6,7,8,9,我们可以查询5678和6789)。
因为这个区间的长度为j - i + 1,所以我们可以取k=log2( j - i + 1),则有:RMQ(A, i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}。
举例说明,要求区间[2,8]的最大值,k = log2(8 - 2 + 1)= 2,即求max(F[2, 2],F[8 - 2 ^ 2 + 1, 2]) = max(F[2, 2],F[5, 2]);
在这里我们也需要注意一个地方,就是<<运算符和±运算符的优先级。
凭借着自创的RMQ算法,超2兔成功当上了国王迎娶了kawaii的兔子公主,走上了人生巅峰,为了纪念和感谢2b小兔子,还专门在王城门口建了一座2b兔的雕像,开始了幸福的生活。

好了,超级小白兔的故事讲完了,下面到了代码实现的时间了。

#include<iostream>
#include <cstdio>
#include<algorithm>
#include <cstring>
#define N 1000
using namespace std;
int maxnum[N][N],minnum[N][N];
int main()
{    
	int num,n,q,i,j,x,y;    
	while(scanf("%d %d",&n,&q) != EOF)    
	{     
	   for(i = 1;i <= n;i++)     
	      {        
	          scanf("%d",&num);     
	                 maxnum[i][0] = minnum[i][0] = num;   //预处理,区间大小为1的数最值就是它本身
	      }
	        for(j = 1; (1<<j) <= n; j++)      
	          {            
	          	for(i = i;i+(1<<j)-1 <= n; i++)     
	                 {           
	                 maxnum[i][j] = max(maxnum[i][j-1],maxnum[i + (1<<(j-1))][j-1]);  	//两个小区间合并为一个大区间             
	               
	            	     minnum[i][j] = min(minnum[i][j-1],minnum[i + (1<<(j-1))][j-1]);
	            	 }                 
	           }
	       	 while(q--)
	                {     
	                      scanf("%d %d",&x,&y); 
	                                 int z = 0;
	                     while(1<<(z+1) <= y-x+1)         z++;          //数是2 的几次方  		
	                     cout<<"max"<<max(maxnum[x][z],maxnum[y-(1<<z)+1][z])<<" ";               
	                     cout<<"min"<<min(minnum[x][z],minnum[y-(1<<z)+1][z])<<endl; 
	                 }
	    }
	    return 0;
	  }
	```
	功德圆满~
	
	
	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值