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表,


ST表算法总结_st表

表示从第i个数起连续

ST表算法总结_预处理_02

,即区间[

ST表算法总结_预处理_03

]内最大值。

显然,初始状态

ST表算法总结_数组_04

,就是第

ST表算法总结_预处理_05

我们把

ST表算法总结_st表

分成两部分(

ST表算法总结_st表

区间有偶数个数),

ST表算法总结_预处理_08

为一段,

ST表算法总结_st表_09

为一段,长度都为

ST表算法总结_数组_10


显然

ST表算法总结_预处理_11


上式就是状态转移方程。

接着是查询,因为st存的状态长度都是

ST表算法总结_数组_12

,但是给出的查询区间长度不一定等于

ST表算法总结_数组_12


给出定理


对于给定区间

ST表算法总结_数组_14



ST表算法总结_数组_15


根据上面定理,

ST表算法总结_预处理_16

肯定刚好大于区间长度的一半,

所以x 到 y 的最大值等于max(从x往后

ST表算法总结_st表_17

的最大值,从y往前

ST表算法总结_st表_17

的最大值)


ST表算法总结_st表_19


k值还可以这样解释,假设拆分成的2个子区间,每个子区间都有2^(k-1)个元素。则2个子区间分别为:

ST表算法总结_st表_20


ST表算法总结_st表_21

。显然必须要满足2个子区间能够完全覆盖[i,j],即


进而可以推导出 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;
}