• 算法分类整理+模板③: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模板
  • 相关阅读:
    ACdream群赛(4) B Double Kings
    ACdream群赛(4)总结
    250E Mad Joe
    ZOJ Monthly, November 2012 I Search in the Wiki
    251C Number Transformation
    253D Table with Letters 2
    Codeforces Round #153 (Div. 2) 总结
    ACdream群赛(4) D Draw a Mess
    ZOJ Monthly, November 2012 G Gao The Sequence
    在vs2005/c++中捕获浮点数异常
  • 原文地址:https://www.cnblogs.com/Torrance/p/5458804.html
Copyright © 2020-2023  润新知