算法分类整理+模板③:RMQ

最开始是打算以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和一维的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]));
}
二维RMQ模板

转载于:https://www.cnblogs.com/Torrance/p/5458804.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值