RMQ问题
定义(来自百度百科)
RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。
例题
n n n个数, m m m次询问,每次询问 a [ i ] a[i] a[i]到 a [ j ] a[j] a[j]中的最大(小)值。
主要实现方法
- 朴素的暴力算法
for(int i=1;i<=m;i++){
int l,r,maxn=-INF;
cin>>l>>r;
for(int j=l;j=r;j++){
maxn=max(maxn,a[j]);
}
cout<<maxn<<endl;
}
优点:实现简单
缺点:单次查询复杂度最坏
O
(
n
)
O(n)
O(n),总复杂度最坏为
O
(
m
n
)
O(mn)
O(mn),会超时
- 线段树等高级数据结构
代码略
优点:可以支持修改,
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)不会超时
缺点:实现复杂
- ST表
优点:实现简单,
O
(
n
l
o
n
g
n
)
O(nlongn)
O(nlongn)预处理,
O
(
1
)
O(1)
O(1)查询
缺点:无法支持修改
ST表实现
当n=
1
0
6
10^6
106时,线段树等
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)级别的数据结构就有一点力不从心,有超时的风险,所以我们需要一种查询为
O
(
1
)
O(1)
O(1)级别的数据结构。
容易想到把每个区间的最值存在一个数组里,每次查询时直接取出。
但这样不但空间存不下,如何
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)预处理也是一个难题。
在这里,我们需要用到一种动态规划的思想。
若将一个区间分成两部分,
1
1
1
−
-
−
a
a
a和
(
a
+
1
)
(a+1)
(a+1)
−
-
−
n
n
n,则最大值
M
a
x
=
m
a
x
(
M
a
x
n
1
−
a
,
M
a
x
n
(
a
+
1
)
−
n
Max=max(Maxn_{1-a},Maxn_{(a+1)-n}
Max=max(Maxn1−a,Maxn(a+1)−n中的最大值。
为了使时间复杂度均分,
a
a
a应取到该区间的中点。
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]为区间
i
i
i
−
-
−
i
+
2
j
i+2^j
i+2j的最值。
则有转移方程式
f
[
i
]
[
j
]
f[i][j]
f[i][j]=
m
a
x
max
max
(
f
[
i
]
[
j
−
1
]
,
f
[
i
+
2
j
−
1
]
[
j
−
1
]
)
(f[i][j-1],f[i+2^{j-1}][j-1])
(f[i][j−1],f[i+2j−1][j−1])
边界条件
f
[
i
]
[
0
]
=
a
[
i
]
f[i][0]=a[i]
f[i][0]=a[i]
然后就可以在 O ( n l o g n ) O(nlogn) O(nlogn)时间里预处理出所有的值了
ST表查询
设给定区间为
L
,
R
L,R
L,R,则将
L
,
R
L,R
L,R分成两段能够覆盖完全整个区间的区间。
但为了查询方便,我们需要契合预处理的数组,将其分为
L
—
—
L
+
2
l
o
g
(
l
e
n
)
L——L+2^{log(len)}
L——L+2log(len)与
R
−
2
l
o
g
(
l
e
n
)
—
—
R
R-2^{log(len)}——R
R−2log(len)——R两个区间,然后即可求出最大值。
细节
由于c++中 l o g log log函数的效率不高,所以我们可以花 O ( n ) O(n) O(n)时间来求出每个数对应的 l o g log log值,方法为 l o g [ n ] = l o g [ n / 2 ] + 1 log[n]=log[n/2]+1 log[n]=log[n/2]+1。
代码
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int RMQ[100005][25],n,m,x,y,Log[100005];
void ST()
{
for(int j=1;(1<<j)<=n;j++)
{
for(int i=1;i+(1<<j)-1<=n;i++)
{
RMQ[i][j]=max(RMQ[i][j-1],RMQ[i+(1<<(j-1))][j-1]);
}
}
}
int ask(int L,int R)
{
int k=Log[R-L+1];
//int k=0;
//while((1<<(k+1))<R-L+1) k++;
//k=(int)(log(R-L+1)/log(2));
return max(RMQ[L][k],RMQ[R-(1<<k)+1][k]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&RMQ[i][0]);
}
for(int i=1;i<=n;i++){
Log[i]=Log[i/2]+1;
}
ST();
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",ask(x,y));
}
}