RMQ问题
RMQ (Range Minimum/Maximum Query):对于长度为n的数组A,回答若干询问RMQ(A,i,j)(i,j<=n-1),返回数组A中下标在i,j范围内的最小(大)值,即求解区间最值
求解方法
线段树 预处理O(n) ~ 查询O(log(n))
ST(Sparse Table)表(本质dp)预处理O(nlog(n)) ~ 查询O(1),不支持在线修改。
ST表算法
首先建立st表,
令
s
t
[
i
]
[
j
]
st[i][j]
st[i][j]表示从第i个数起连续
2
j
2 ^ j
2j 个数中的最大值,即区间[
i
,
i
+
2
j
−
1
i, i+2^j-1
i,i+2j−1]内最大值。
显然,初始状态
s
t
[
i
]
[
0
]
=
a
[
i
]
st[i][0] = a[i]
st[i][0]=a[i],就是第
i
i
i 个数本身。
我们把
s
t
[
i
]
[
j
]
st[i][j]
st[i][j]分成两部分(
s
t
[
i
]
[
j
]
st[i][j]
st[i][j] 区间有偶数个数),
[
i
,
i
+
2
j
−
1
−
1
]
[i, i+2 ^ {j-1}-1]
[i,i+2j−1−1]为一段,
[
i
+
2
j
−
1
,
i
+
2
j
−
1
]
[i+2^{j-1}, i+2^{j}-1]
[i+2j−1,i+2j−1]为一段,长度都为
2
j
−
1
2^{j-1}
2j−1,
显然
s
t
[
i
,
j
]
=
m
a
x
(
s
t
[
i
,
j
−
1
]
,
s
t
[
i
+
2
j
−
1
,
j
−
1
]
)
st[i , j] = max(st[i , j-1],st[ i + 2^{j-1}, j-1])
st[i,j]=max(st[i,j−1],st[i+2j−1,j−1])
上式就是状态转移方程。
接着是查询,因为st存的状态长度都是
2
j
2^j
2j,但是给出的查询区间长度不一定等于
2
j
2^j
2j,
给出定理
2
⌊
l
o
g
2
a
⌋
>
a
2
2^{\lfloor log_2{a} \rfloor} > \frac{a}{2}
2⌊log2a⌋>2a
对于给定区间
[
x
,
y
]
[x, y]
[x,y],
设
k
=
⌊
l
o
g
2
(
y
−
x
+
1
)
⌋
k =\lfloor log_2{(y-x+1)} \rfloor
k=⌊log2(y−x+1)⌋
根据上面定理,
2
k
2^k
2k 肯定刚好大于区间长度的一半,
所以x 到 y 的最大值等于max(从x往后
2
k
2^{k}
2k 的最大值,从y往前
2
k
2^{k}
2k的最大值)
即
r
m
q
(
x
,
y
)
=
m
a
x
(
s
t
[
x
]
[
k
]
,
s
t
[
y
−
2
k
+
1
]
[
k
]
)
rmq(x, y) = max(st[x][k] , st[y-2^k+1][k])
rmq(x,y)=max(st[x][k],st[y−2k+1][k])
k值还可以这样解释,假设拆分成的2个子区间,每个子区间都有2^(k-1)个元素。则2个子区间分别为:
[
i
,
2
k
−
1
+
i
−
1
]
[i, 2^{k-1} + i -1]
[i,2k−1+i−1]和
[
j
−
2
k
−
1
+
1
,
j
]
[j - 2^{k-1} + 1,j]
[j−2k−1+1,j]。显然必须要满足2个子区间能够完全覆盖[i,j],即
(
2
k
−
1
+
i
−
1
)
+
1
>
=
(
j
−
2
k
−
1
+
1
)
(2^{k-1} + i -1) + 1 >= (j - 2^{k-1} +1)
(2k−1+i−1)+1>=(j−2k−1+1)
进而可以推导出 2^k >= (j - i + 1)。这样的话,我们取满足条件的K最小值就可以了。为什么要取最小值呢?是为了保证2个子区间长度尽可能的短。
例题51nod1174
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define d(x) cout << (x) << endl
#pragma GCC diagnostic error "-std=c++11"
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e4 + 10;
int n;
int a[N];
int st[N][20];
void rmq()
{
for(int j = 1; (1 << j) <= n; j++){ //按列填表
for (int i = 0; i + (1 << j) - 1 < n; i++){
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
int ask(int l, int r) //查询区间最值
{
int k = log2(r - l + 1);
return max(st[l][k], st[r - (1 << k) + 1][k]);
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i++){
scanf("%d", &a[i]);
st[i][0] = a[i]; //初始化
}
rmq();
int q;
cin >> q;
while(q--){
int i, j;
cin >> i >> j; //询问区间[i, j]
cout << ask(i, j) << endl;
}
// d(st[0][1]);
return 0;
}