RMQ问题与ST算法

对于问题(RMQ,Random Maximum Query):给你一串固定、不修改的数字,询问多次某个区间内的最大值或者最小值。

用朴素的想法,依次遍历得到答案,时间复杂度为O(n),但是对于多次询问,如1E6次询问,这样的算法就不是最优的了。所以出现了ST算法。

ST算法的原理是动态规划,通过O(logn)的预处理dp数组,得到O(1)的询问。

1.预处理

假设dp[i,j]表示从a[i]a[i+2^j-1]这个范围内的最大值,即以a[i]为起点连续2^j个数的最大值。

我们把它一分为二,每段元素都为2^(j-1)个数字。也就是说dp[i,j]分为dp[i, j - 1]和f[i + 2^( j - 1), j- 1]两部分(自己代入一下dp的定义)。

所以我们很容易得到状态转一方程dp[i][j] = max(dp[i][j - 1], dp[i + 2^(j-1)][j - 1]);

我们在给dp[i][0]初始化一下均为a[i]。预处理完毕。

2.询问

先引入一个k值,每个区间均为2^(k-1)个数字,所以两部分区间为[i, 2^(k-1) + i -1]和[j - 2^(k-1) + 1,j]。显然必须要满足2个子区间能够完全覆盖[i , j],即要求(2^(k-1) + i -1) + 1 >= (j - 2^(k-1) +1),易得2^k >= (j - i + 1),取k的最小值,使2个子区间尽可能的短。所以k = log2(j - i + 1);(向下取整)

所以对于每次询问易得ans = max(dp[i][k],dp[j - 2^k + 1][k])。

3.小技巧 

cmath中log2函数效率较低,所以可以用递推处理log2d的值,我们用lo代表log2d向下取整的值,则lo[d] = lo[d/2]+1。

/*
输入一串数字,给你M个询问,每次询问就给你两个数字X,Y,要求你说出X到Y这段区间内的最大数。
输入:
输入n,m。其中n个数字,m次询问
10 2
3 2 4 5 6 8 1 2 9 7
1 4
3 8
输出:
5
8
*/

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5, LogN = 20;
int lo[N], dp[N][LogN + 5], a[N];
int main()
{
	int n, m, x, y;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	lo[0] = - 1;//这样lo[1]才可以是0
	for (int i = 1; i <= n; i++)
		dp[i][0] = a[i], lo[i] = lo[i >> 1] + 1;//初始化dp[i][0]即a[i]到a[i]的最大值为a[i];递推得到log2[d] = log[d/2]+1(表示log2d向下取证的值为)
	for (int j = 1; j <= LogN; j++)
		for (int i = 1; i + (1 << j) - 1 <= n; i++)
			dp[i][j] = max(dp[i][j - 1], dp[i + (1 << j - 1)][j - 1]);//根据状态转移方程dp[i][j] = max(dp[i][j-1],dp[i+2^(j-1)][j-1])
	while (m--)
	{
		scanf("%d%d", &x, &y);
		int k = lo[y - x + 1];
		printf("%d\n", max(dp[x][k], dp[y - (1 << k) + 1][k]));
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/waterboy_cj/article/details/81509158
今日推荐