RMQ算法是什么?
以我个人的看法,与其说RMQ是算法,还不如说是一类问题,即区间(Range)最大/小值(Minimum/Maximum)查询(Query)
有三种办法可以解决这类问题
第一种是暴力查找
for(int i=n1,i<=n2;i++){
//每次寻找是O(n)的时间复杂度
}
如果查找次数是0<n<len,那整个过程就是o(n^2)级别
预计数组规模是1e5左右就超时了
第二种
线段树o(nlogn)???这是个啥,不懂,回头再说
第三种
ST方法,可以在o(nlogn)的预处理之后做到o(1)的查询速度
原理大概是二分吧
RMQ的ST方法是怎么工作的?
分成两部分:预处理和查询
难点是循环的控制(贼难受,思考了很长时间才稍微理解)
先说预处理:
以最大值为例:
假设有数组arr,数组长度len
那么我们需要来一个二维数组dp[i][j];
i 是1<=i<=len的规模
j 是log2(len)的规模
为啥?
因为dp[i][j]的含义是从下标为i的字符开始,往后2j个字符的最大值
也就是dp[i][j] = for(arr[i]到arr[i+2j-1])的最大值
那么dp[i][0] 就是从arr[i]开始20=1个字符的最大值,这里需要说一下,我们不用0下标,也就是说dp[i][0]就是arr[i]
RMQ的时间空间复杂度?
O(nlogn)
RMQ算法的预处理是怎么处理的?
通过一个双重循环
for(int j=1;(1 << j )<=len;j++)//很奇怪是吧?为什么和以往把i放在外面的思路不一样呢?
for(int i=1;i + (1 << j) - 1<=len;i++)
dp[i][j] = max(dp[i][j-1],dp[i + (1 << j-1)][j-1]);
把数组原模原样的弄到dp中是这个样子的
之后我们把j放在外面,把i放在里面做循环,为的是让数组一列一列的dp而不是一行一行的dp,如果把i放在外面,情况就是这样的:
dp[1][1] = max(dp[1][0],dp[1+1][0]);没问题,等于1
dp[1][2] = max(dp[1][1],dp[1+2][1]);dp[1][1]是有了,但是dp[3][1]不是还没加工吗?
dp思想:也就是说,每段dp元素的值是之前已经处理过的两个元素之间的关系得出来的
然后我们先抽象的理解下这段原理
dp[i][k],也就是说把从i开始到i+2k-1个元素的最值找到并存到dp[i][k]中去,
那么我们可以吧这段分两份一起找
一份是从 i 到 i +2k-1-1
这不就是dp[i][k-1]吗
一份是从i + 2k-1到 i + 2k-1
两份长度都是2k-1
长度一样, j 值一样, i 加上 2k-1
就是dp[i+(1 << j-1)][j-1]的来历
正好凑出2k个元素
举例 dp[2][3] = max(dp[2][2] , dp[2+22][2])
黄色代表dp[2][2]的整体,绿色代表dp[2 + 22][2]的整体,dp[2][3]最后成绿色代表绿色找出来的值更大
RMQ的查询:
int find(int n1,int n2){//我们假设查询最大值
int k = log2(n2-n1+1);//k是log2区间长度
int ans = max(dp[n1][k],dp[n2-(1<<k)+1][k]);
return ans;
}
k的作用是把区间的长度对2取对数的向下取整
找到区间n1的 j 标
这样就不把从n1开始到下标n1+2n-1的值全都检索一遍了吗
那么问题来了:
既然是向下取整,2k一定是小于等于n2-n1+1的,缺少的一部分怎么办?
我们可以直接从n2往前找
不会把,不是说 j 标是往后2j个吗?
对,所以我们直接找n2前面2k的值就可以了
也就是**dp[n2-(1<<k)+1][k]**的来历,不加+1就可能检索不到下标n2
(+1是啥我不想说了前面解释dp的 j 标的时候仔细说过了…应该能懂吧)
来张图片感受一下
RMQ经典题目(做到题会回来跟新的):
poj3264
uva 11235(我进不去uva网站。。。不会翻墙)