RMQ(range minV/maxV query)算法是一个快速求区间最值得离线算法,预处理时间为O(nlgn),查询时间为O(1)。这个问题也可以用线段树解决。
问题描述:给出n个数ary[i],快速求出某一区间的最值。
算法思想:DP + 位运算
算法分析,状态:dp[i][j] 表示第i为开始,到(i + 2^j - 1)位的最大/小值。
状态转移方程建立:dp[i][j]可以分为两部分,(i, i + 2^(j-1) - 1), (i + 2^(j-1), i + 2^j - 1)
其实就是把(i, i + 2 ^j)这个区间分为了两部分,没一部分长度为2^(j - 1)。
状态转移方程:dp[i][j] = max(dp[i][j-1], dp[i+(1<<(j-1))][j-1]。(这是求最大值的,若求最小值,把max()换为min()即可)
//需要一个一维待查询的数组,及其长度n,两个状态数组。
void RMQ() {
for (int i = 1; i <= n; ++i) {
dpMax[i][0] = dpMin[i][0] = ary[i];
}
for (int j = 1; (1 << j) <= n; ++j) {
for (int i = 1; i + (1 << j) - 1 <= n; ++i) {
dpMax[i][j] = max(dpMax[i][j - 1], dpMax[i + (1 << (j - 1))][j - 1]);
dpMin[i][j] = min(dpMin[i][j - 1], dpMin[i + (1 << (j - 1))][j - 1]);
}
}
}
那么查询的时候对于任意一个区间 L -- R ,我们同样可以得到区间差值 len = (R - L + 1)。
那么我们这一用小于2^k<=len,的 k 把区间分成可以交叉的两部分L 到 L+2^(k)- 1, 到 R -(1<
查询代码:
int queryRMQ(int L, int R) {
int k = 0, len = R - L + 1;
while ((1 << (k + 1)) <= len)
k++;
int maxV = max(dpMax[L][k], dpMax[R - (1 << k) + 1][k]);
int minV = min(dpMin[L][k], dpMin[R - (1 << k) + 1][k]);
return maxV - minV; //返回最大值还是最小值,还是最大最小值的差,视情况而定
}
例题:POJ3264
RMQ 解法(TIME: 1657MS)
#include
#include
#define N 20
using namespace std;
const int maxn = 5e4 + 7;
int ary[maxn], n, q;
int dpMax[maxn][N], dpMin[maxn][N];
void RMQ() {
for (int i = 1; i <= n; ++i) {
dpMax[i][0] = dpMin[i][0] = ary[i];
}
for (int j = 1; (1 << j) <= n; ++j) {
for (int i = 1; i + (1 << j) - 1 <= n; ++i) {
dpMax[i][j] = max(dpMax[i][j - 1], dpMax[i + (1 << (j - 1))][j - 1]);
dpMin[i][j] = min(dpMin[i][j - 1], dpMin[i + (1 << (j - 1))][j - 1]);
}
}
}
int queryRMQ(int L, int R) {
int k = 0, len = R - L + 1;
while ((1 << (k + 1)) <= len)
k++;
int maxV = max(dpMax[L][k], dpMax[R - (1 << k) + 1][k]);
int minV = min(dpMin[L][k], dpMin[R - (1 << k) + 1][k]);
return maxV - minV; //返回最大值还是最小值,还是最大最小值得差,视情况而定
}
int main()
{
while(~scanf("%d%d", &n, &q)) {
for (int i= 1; i <= n; ++i) {
scanf("%d", ary + i);
}
RMQ();
while(q-- > 0) {
int L, R;
scanf("%d%d", &L, &R);
printf("%d\n", queryRMQ(L, R));
}
}
return 0;
}
线段树解法(TIME: 1563MS)
#include
using namespace std;
const int INF=0x7fffffff;
const int maxn=800010;
int minV=INF;
int maxV=-INF;
struct Node{
int L , R;
int minV,maxV;
int Mid(){
return (L+R)/2;
}
}tree[maxn];
void BuildTree(int root,int L,int R){
tree[root].L=L;
tree[root].R=R;
tree[root].minV=INF;
tree[root].maxV=-INF;
if(L!=R){
BuildTree(2*root+1,L,(L+R)/2);
BuildTree(2*root+2,(L+R)/2+1,R);
}
}
void Insert(int root,int i,int v){
if(tree[root].L==tree[root].R){
tree[root].minV=tree[root].maxV=v;
return;
}
tree[root].minV=min(tree[root].minV,v);
tree[root].maxV=max(tree[root].maxV,v);
if(i<=tree[root].Mid()) Insert(2*root+1,i,v);
else Insert(2*root+2,i,v);
}
void Query(int root,int s,int e){
if(tree[root].minV>=minV&&tree[root].maxV<=maxV) return;
if(tree[root].L==s&&tree[root].R==e){
minV=min(minV,tree[root].minV);
maxV=max(maxV,tree[root].maxV);
return;
}
if(e<=tree[root].Mid()) Query(2*root+1,s,e);
else if(s>tree[root].Mid()) Query(2*root+2,s,e);
else{
Query(2*root+1,s,tree[root].Mid());
Query(2*root+2,tree[root].Mid()+1,e);
}
}
int main(){
int n,q,h;
int i,j,k;
scanf("%d%d",&n,&q);
BuildTree(0,1,n);
for(i=1;i<=n;i++){
scanf("%d",&h);
Insert(0,i,h);
}
for(i=0;i