链接
题意
给出N个整数,Q个询问,每个询问给出一个区间[l, r],要求得出区间内整数的最大值与最小值之差。
思路
典型的一个RMQ问题,之前是用线段树做的,这次用st表试一下。
st表是专门用来处理区间最值查询的数据结构,它跟线段树不同,线段树是在O(n)内构建,单次查询/更新为O(log(n))的复杂度,但是线段树能处理更复杂的问题,比如更新整个区间(lazy标记)和对区间做计数统计。
st表对长度为n的序列,可在O(n*log(n))内构建完毕,之后对任意区间[l, r]查询复杂度均为O(1),但是st表的功能没有那么强大,只能解决区间最值的查询,对于“区间内有多少个元素满足条件”这样的问题就无法解决。
另一个类似的结构是树状数组,是一种“精简版”的线段树,可用来做单点更新和区间查询,但是更复杂的“区间更新”问题就无法解决。
关于st表
/*
* st表
* st[i][j]表示元素arr[i]开始,长度为2^j区间内的最值
* st[i][0]为arr[i],st[i][j] = max(st[i][j-1], st[i - (1 << j-1)][j-1])
* 求区间[l, r]内最值:
* log2[i]代表i的对数向下取整
* 对于长度len而言,显然2^log2[len]严格大于len的一半
* 令k = log2[r - l + 1],则最值为max(st[l][k], st[r - (1<<k) + 1][k])
*/
#define maxn (55000)
int log2[maxn], st[maxn][32];
void st_prepare(int n, int *arr)
{
log2[1] = 0;
for(int i = 2; i <= n; i++)
{
log2[i] = log2[i - 1];
if((1 << log2[i] + 1) == i)
log2[i]++;
}
for(int i = n; i >= 1; i--)
{
st[i][0] = arr[i];
for(int j = 1; i + (1 << j) - 1 <= n; j++)
{
st[i][j] = max(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}
}
}
int st_query(int l, int r)
{
int len = r - l + 1, k = log2[len];
return max(st[l][k], st[r - (1 << k) + 1][k]);
}
代码
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
#define maxn (55000)
int log2[maxn], mx[maxn][32], mn[maxn][32];
void st_prepare(int n, int *arr)
{
log2[1] = 0;
for(int i = 2; i <= n; i++)
{
log2[i] = log2[i - 1];
if((1 << log2[i] + 1) == i)
log2[i]++;
}
for(int i = n; i >= 1; i--)
{
mx[i][0] = mn[i][0] = arr[i];
for(int j = 1; i + (1 << j) - 1 <= n; j++)
{
mx[i][j] = max(mx[i][j - 1], mx[i + (1 << j - 1)][j - 1]);
mn[i][j] = min(mn[i][j - 1], mn[i + (1 << j - 1)][j - 1]);
}
}
}
int st_query(int l, int r)
{
int len = r - l + 1, k = log2[len];
return max(mx[l][k], mx[r - (1 << k) + 1][k]) - min(mn[l][k], mn[r - (1 << k) + 1][k]);
}
int cow[maxn];
int main()
{
int N, Q, L, R;
cin >> N >> Q;
for(int i = 1; i <= N; i++)
scanf("%d", &cow[i]);
st_prepare(N, cow);
while(Q--)
{
scanf("%d%d", &L, &R);
printf("%d\n", st_query(L, R));
}
return 0;
}