所谓RMQ问题,就是求区间最值的问题
这里先给出一道例题 :
题目描述
老管家是一个聪明能干的人。他为财主工作了整整10年,财主为了让自已账目更加清楚。要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,问题是这样的:在a到b号账中最少的一笔是多少?为了让管家没时间作假他总是一次问多个问题。
输入输出格式
输入格式:
输入中第一行有两个数m,n表示有m(m<=100000)笔账,n表示有n个问题,n<=100000。
第二行为m个数,分别是账目的钱数
后面n行分别是n个问题,每行有2个数字说明开始结束的账目编号。
输出格式:
输出文件中为每个问题的答案。具体查看样例。
输入输出样例
输入样例#1:
10 3 1 2 3 4 5 6 7 8 9 10 2 7 3 9 1 10
输出样例#1:
2 3 1
sol :
首先,我们可以想到用线段树去维护区间最值,那么这就是道模板题
但是,其实还有一个专门为RMQ问题设计的算法:ST算法
ST算法的核心其实是dp(也用到了二分的思想)
假设我们维护的是区间最小值,我们用数组minn[i][j]表示以开头之后连续2^j个数之中的最小值
那么初始化过程就是 minn[i][0] = a[i] (a[]为原数组)
我们可以推出状态转移方程 minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]); (这里用到了位运算,1<<j表示的就是2^j)
那么具体的转移过程就可以写出来了 :
for(int j=1;1<<j<=m;j++) (想一下为什么是j循环放在外面?)
for(int i=1;i+(1<<j)-1<=m;i++)
minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
(其实思路就是把两个长度为2^(j-1)的区间合并成一个长度为2^j的区间,所以第一层循环当然是j啦=-=)
最后再讲一下查询的问题
假设我们要查找的区间的长度不是2^x,比如我们要查找区间[2,6]的最小值,那么该怎么办呢?
我们观察到要查询的区间长度为5,而我们维护了长度为2^x的区间所以我们可以查询区间[2,5]和区间[3,6]的最小值,然后取小就可以了
综上,ST算法构造区间最值时的时间复杂度为O(nlogn),单次查询的复杂度为O(1)
下面附例题代码:(ST算法)
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; int m,n; int minn[100001][22] ; void RMQ_init() { for(int j=1;(1<<j)<=m;j++) for(int i=1;i+(1<<j)-1<=m;i++) minn[i][j] = min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]); } int RMQ_ask(int l,int r) { int k = log2(r-l+1) ; return min(minn[l][k],minn[r-(1<<k)+1][k]) ; } int main() { cin>>m>>n; for(int i=1;i<=m;i++) scanf("%d",&minn[i][0]) ; RMQ_init() ; int x,y ; for(int i=1;i<=n;i++) { scanf("%d%d",&x,&y) ; printf("%d ",RMQ_ask(x,y)) ; } return 0; }
——end;