最开始是打算以LCA作为第三篇算法整理的,但是由于学习LCA时发现自己的RMQ学习的不够扎实,所以先复习一下RMQ。本文感谢队友某淞的学习笔记。
最初看到RMQ模板的时候感觉好高端,感觉里面的各种数组,位移运算非常复杂。所以对于任何算法的学习我觉得都要分为以下的几步进行:
1、了解这种算法能解决什么类的问题。2、知道这种算法的最坏时间复杂度和期望时间复杂度,今后看到题的时候能通过数据范围去快速去判定某些题的解法。3、了解算法的思想和最核心的代码。4、最后就是用由浅入深的题目进行训练,直到完全掌握。
书归正传,RMQ首先翻译成中文是区间最值问题,顾名思义,我们要利用这个算法求解的是某一个区间的最大值或最小值的问题,而RMQ的实现其实有很多种办法,最简单的就是暴力法,对于F数组求F[a,b]的最值时,从头到尾遍历一次,就可以找出其相应的最值。单次遍历的时间复杂度是O(n)的,但是如果有q次询问,则总复杂度为O(nq),在一般情况下都是超时的。第二种是线段树法,利用优秀的数据结构线段树进行求解,利用线段树的性质维护区间的最值,具体算法会在线段树中详细整理,而利用线段树的时间复杂度,单次查询为O(lgn),q次操作为O(qlgn),但是线段树可以做到以O(lgn)的复杂度进行单点或者区间修改,当题目中涉及到区间更新维护最值的问题时,一般都是利用线段树进行。第三种算法就是本文要详细介绍的ST算法,其本质为动态规划,可以在O(nlgn)的时间复杂度下对原数组进行预处理,然后以O(1)的时间复杂度进行查询,十分快捷方便。最后一种是LCA和RMQ的相互之间的转化,先将RMQ规约成LCA,再规约成约束RMQ。我感觉这种求解方式意义不大,作为了解即可,毕竟有ST或者线段树这样的优秀算法进行RMQ求解,没有必要多此一举。
对于ST算法:
其本质是DP的思想,对于数组F,利用一个辅助数组dp,dp[i,j]表示从F[i]开始的1<<j个数中的最值,即:从F[i]到F[i+(1<<j)-1]。比如,对于dp[5,2]表示F[5]到F[5+4-1]即F[8]的最值。现在我们来看下如何对于dp数组进行状态转移,假设我们要求F[5]到F[12]的最值,而F[5]到F[12]我们可以表示为dp[5,3],接着我们可以把区间拆分成:F[5]到F[8]和F[9]到F[12],即:dp[5,2]和dp[9,2]。所以状态转移方程就很容易写出来了:dp[i,j]=max(or min)(dp[i,j-1],dp[i+(1<<(j-1)),j-1])。
而在查询的时候,我们可以这样考虑,如果对于某一个区间F[a,b],假设有一个中间点c,还有一个常数d<(b-a)/2,我们可以把F[a,b]拆分成F[a,c+d]与F[c-d,b]。或者说,我们在分解区间的时候不一定非要完全不重复的分解,因为我们求解的是区间最值,即使分解之后有重复元素,也不影响最值。所以对于区间F[a,b]我们只要选择一个以a为头的区间和一个以b为尾并且重复元素数量大于等于0的区间即可。举个例子,我们想查询F[5,11]的最值,我们可以分解成F[5,8]和F[8,11],前面我们已经说了,对于存在重复元素是不影响最后的最值的。所以对于分解后的区间,可以用辅助数组中的dp[5,2]和dp[8,2]来表示,所以我们的查询就只要找到dp[5,2]和dp[8,2]中的最值即可。
const int MAXN=50010; int dp[MAXN][20]; int mm[MAXN]; //对于log的优化,存在数组中 void init_RMQ(int n,int *f){ mm[0]=-1; for(int i=1;i<=n;i++){ mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1]; dp[i][0]=f[i]; } for(int j=1;j<=mm[n];j++){ for(int i=1;i+(1<<j)-1<=n;i++){ dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); } } } int RMQ(int x,int y){ int k=mm[y-x+1]; return max(dp[x][k],dp[y-(1<<k)+1][k]); }
二维RMQ和一维的RMQ原理相同,理解后抄模板即可:
int val[300][300]; int dp[300][300][10][10]; int mm[300]; void init_RMQ(int n,int m){ mm[0]=-1; for(int i=1;i<=300;i++){ mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1]; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) dp[i][j][0][0]=val[i][j]; for(int ii=0;ii<=mm[n];ii++){ for(int jj=0;jj<=mm[m];jj++){ if(ii+jj){ for(int i=1;i+(1<<ii)-1<=n;i++){ for(int j=1;j+(1<<jj)-1<=m;j++){ if(ii) dp[i][j][ii][jj]=max(dp[i][j][ii-1][jj],dp[i+(1<<(ii-1))][j][ii-1][jj]); else dp[i][j][ii][jj]=max(dp[i][j][ii][jj-1],dp[i][j+(1<<(jj-1))][ii][jj-1]); } } } } } } int RMQ(int x1,int y1,int x2,int y2){ int k1=mm[x2-x1+1]; int k2=mm[y2-y1+1]; x2=x2-(1<<k1)+1; y1=y2-(1<<k2)+1; return max(max(dp[x1][y1][k1][k2],dp[x1][y2][k1][k2]),max(dp[x2][y1][k1][k2],dp[x2][y2][k1][k2])); }