RMQ(ST算法)

RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列a,回答若干询问RMQ(A,i,j)(i, j<=n),返回数列a中下标在i,j之间的最小/大值。如果只有一次询问,那样只有一遍for就可以搞定,但是如果有许多次询问就无法在很快的时间处理出来。在这里介绍一个在线算法。所谓在线算法,是指用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。ST(Sparse Table)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

步骤如下:

假设a数组为:

1, 3, 6, 7, 4, 2, 5

1.首先做预处理(以处理区间最小值为例)

设mn[i][j]表示从第i位开始连续2^j个数中的最小值。例如mn[2][1]为第2位数开始连续2个的数的最小值,即3, 6之间的最小值,即mn[2][1] = 3;

之后我们很容想到递推方程:

mn[i][j] = min(mn[i][j - 1], mn[i + (1 << j - 1)][j - 1])

附上伪代码:

for(int j = 0; j < 20; j ++)
    for(int i = 1; i + (1 << j) <= n + 1; i ++)
        mn[i][j] = min(mn[i][j - 1], mn[i + (1 << (j - 1))][j - 1]);

咦?为什么第二行是i + (1 << j) <= n + 1呢?因为mn[i][j]表示连续2^j个数,所以mn[i][j]所维护的区间为[i, i + (1 << j) - 1],所以在最后要+1,其实是为了方便,写成i + (1 << j) - 1 <= n感觉左边太长了,所以写在右边了。

那么为什么j要写在外围?如果写在里面的输出结果是这样的

我们会发现没有更新过,这是为什么呢? 因为我们在更新的时候是通过要通过2^(j - 1)的区间来更新2^j的区间,来看状态转移方程:

mn[i][j] = min(mn[i][j - 1], mn[i + (1 << j - 1)][j - 1])

我们发现如果j写在里面的话,在更新mn[i][j]的时候会发现mn[i +(1<<j - 1)][j - 1]还没有更新,所以才会出现这样的结果,正确结果如下:

咦?为什么还有0?我们来看伪代码:

for(int j = 0; j < 20; j ++)
    for(int i = 1; i + (1 << j) <= n + 1; i ++)
        mn[i][j] = min(mn[i][j - 1], mn[i + (1 << (j - 1))][j - 1]);

看第二行会发现,对于i + (1  << j) - 1超过n的,我们没有更新,如图中的mn[5][2],5 + 2^2 - 1 = 8 > 7所以没有更新,但这并不影响询问的结果。

2.查询

假设我们需要查询区间[l, r]中的最小值,令k = log2(r - l + 1); 则区间[l, r]的最小值RMQ[l,r] = min(mn[l][k], mn[r - (1 << k) + 1][k]);

那么为什么这样就可以保证为区间最值吗?

mn[l][k]维护的是[l, l + 2 ^ k - 1], mn[r - (1 << k) + 1][k]维护的是[r - 2 ^ k + 1, r] 。

那么我们只要保证r - 2 ^ k + 1 <= l + 2 ^ k - 1就能保证RMQ[l,r] = min(mn[l][k], mn[r - (1 << k) + 1][k]);

我们用分析法来证明下:

若r - 2 ^ k + 1 <= l + 2 ^ k - 1;

则r - l + 2 <= 2 ^ (k + 1);

又因为 k = log2(r - l + 1);

则r - l + 2 <= 2 *(r - l + 1);

则r - l >= 0;

显然可得。

由此得证。

我们来举个例子 l = 4, r = 6;

此时k = log2(r - l + 1) = log2(3) = 1;

所以RMQ[4, 6] = min(mn[4][1], mn[5][1]);

mn[4][1] = 4, mn[5][1] = 2;

所以RMQ[4, 6] = min(mn[4][1], mn[5][1]) = 2;

我们很容易看出来了答案是正确的。

附上总代码:(以结构体的形式写出):

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 const int N = 100000 + 5;
 5
 6 int a[N];
 7
 8 int mn[N][25];
 9
10 int n, q, l, r;
11
12 struct RMQ{
13     int log2[N];
14     void init(){
15         for(int i = 0; i <= n; i ++)log2[i] = (i == 0 ? -1 : log2[i >> 1] + 1);
16         for(int j = 1; j < 20; j ++)
17             for(int i = 1; i + (1 << j) <= n + 1; i ++)
18                 mn[i][j] = min(mn[i][j - 1], mn[i + (1 << j - 1)][j - 1]);
19     }
20     int query(int ql, int qr){
21         int k = log2[qr - ql + 1];
22         return min(mn[l][k], mn[r - (1 << k) + 1][k]);
23     }
24 }rmq;
25
26 void work(){
27     rmq.init();
28     scanf("%d", &q);
29     while(q --){
30         scanf("%d%d", &l, &r);
31         printf("%d\n", rmq.query(l, r));
32     }
33 }
34
35 int main(){
36     while(scanf("%d", &n) == 1){
37         for(int i = 1; i <= n; i ++)scanf("%d", a + i), mn[i][0] = a[i];
38         work();
39     }
40     return 0;
41 }

参考论文:http://blog.csdn.net/niushuai666/article/details/6624672/

时间: 2024-12-24 05:15:10

RMQ(ST算法)的相关文章

[POJ3264]Balanced Lineup(RMQ, ST算法)

题目链接:http://poj.org/problem?id=3264 典型RMQ,这道题被我鞭尸了三遍也是醉了…这回用新学的st算法. st算法本身是一个区间dp,利用的性质就是相邻两个区间的最值的最值一定是这两个区间合并后的最值,这条性质决定了这个dp子问题的重叠.可以利用这个性质预处理出这张表,只不过步长是2的幂次. 查询的时候也是如此,但是未必会精准地选中两个区间,不要紧,因为两个区间重叠的部分也会被自动算在求最值的内部.这个时候如果算的是区间和的话,要减去这一部分.(区间和的话直接用前

CF359D Pair of Numbers [RMQ+ST算法]

题意: 给一串数,找出最长的区间使得这个区间里面有个数能被其他所有数整除(包括它自己),求满足这个条件的最长区间的个数及长度,以及这些区间的左端的位置 分析: 这个区间的要求其实就是GCD(ALL)=MIN(ALL),能被其他数整除,这个数肯定是最小的,然后又能被其他数整除(包括自己)这个数就是GCD了 可以二分枚举区间长度,然后验证答案的可靠性 对当前长度的所有区间,套用RMQ,验证是否存在一个区间的GCD=MIN 如果有这样的一个区间,那么说明当前长度可以,加大枚举的区间长度,否则减小 #i

POJ3264 Balanced Lineup 线段树 RMQ ST算法应用

Balanced Lineup Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 36813 Accepted: 17237 Case Time Limit: 2000MS Description For the daily milking, Farmer John's N cows (1 ≤ N ≤ 50,000) always line up in the same order. One day Farmer John de

POJ 3368 Frequent values RMQ ST算法/线段树

                                                     Frequent values Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 15229   Accepted: 5550 Description You are given a sequence of n integers a1 , a2 , ... , an in non-decreasing order. In

自己写的 RMQ ST算法模板类

1 #include<iostream> 2 using namespace std; 3 #include<cstdio> 4 #include<cstring> 5 /* 6 说明: 7 RMQ<T> rr;定义一个查询区间最小值的数据类型为T 的类 8 SetMaxn(T maxn);设置初始化数组的最大值 9 Creat(T a[],int maxn) 设置查询的数组,和数组长度,从0开始 10 Getx(int la,int lb) 查询数组中下标

RMQ st算法 区间最值模板

#include<bits/stdc++.h> const int N=1e6+5; const int Logn=20; int f[N][Logn],a[N],lg[N],n,m; int main(){ cin>>n>>m; rep(i,1,n) cin>>a[i]; lg[0]=-1; rep(i,1,n) fa[i][0]=a[i],lg[i]=lg[i>>1]+1; rep(j,1,Logn) for(int i=1;i+(1<

RMQ问题——ST算法

什么是RMQ.ST:RMQ(Range Minimum/Maximum Query)问题,即求区间的最值.可以写一个线段树来实现,但是每次查询的时间复杂度为O(log n),若查询次数过多则可能超时.ST算法是一种离线算法,经过O(nlogn)的预处理后,可以在O(1)的时间复杂度内进行查询,缺点是无法对数据做出修改. 算法实现: 初始化:用dp实现初始化.a[]为原始数据数组f,[i][j]表示从i向后的2j个数字中的最值.显然f[i][0]=a[i]; 我们将f[i][j]分为两段,一段为a

理解RMQ问题和ST算法的原理

下图为TI C6xx DSP Nyquist总线拓扑图,总线连接了master与slave,提供了高速的数据传输.有很多种速率不同的总线,如图中的红色方框,最高速总线为CPU/2 TeraNet SCR(即VBUSM SCR),带宽为256bit,其他低速总线为CPU/3,CPU/6,带宽参考图中所示.总线之间用Bridge(桥)连接,作用包括转换总线的速率,使之与所流向总线的速率相同等. 在具体应用中,各种速率的总线完全可以满足复杂的数据传输,而数据传输的瓶颈往往在于连接总线之间的Bridge

RMQ问题之ST算法

RMQ问题之ST算法 RMQ(Range Minimum/Maximum Query)问题,即区间最值问题.给你n个数,a1 , a2 , a3 , ... ,an,求出区间 [ l , r ]的最大值. 举例:a={ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 },求出区间[4 ,8]中的最值.(答案:8 ) 这个问题最朴素的想法是用一个循环每次比较大小,但是,当数据范围较大时,这个算法十分低效.这时我们往往使用 ST 算法解决这个问题.虽然线段树和树状数组都能解决,但

LCA最近公共祖先 ST+RMQ在线算法

对于这一类的问题有2中解决方法.第一种就是tarjan的离线算法,还有一中是基于ST算法的在线算法.复杂度都是O(n); 先介绍在线算法: 1) dfs: 对于图所示的树,我们从根节点1开始dfs,按照先序访问(不算完全的先序),那么它访问顺序就是1 -> 2 -> 4 -> 2 -> 5 -> 7 -> 5 -> 8 -> 5 -> 2 -> 1 -> 3 -> 1 然后用数组first存第一次访问到该点时的时间(也就是访问顺序里面