ST表实现RMQ 动态规划思想

做题时如果要频繁查询一个区间的最大值或者最小值,是非常消耗时间的,但是预处理打st表(dp数组)的话,可以实现用常数时间查询区间最值

ST表状态定义

dp[i][j]表示以 i 下标为区间左端点,包含 2j 个元素的区间的最大元素的值

dp[i][j] = 区间 [i, i+2^j-1] 的最大元素的值

这么定义的好处是:对于一个区间左端点,我们花费O(log(n))的时间就可以列举出以它为左端点,长度为2的幂次的区间的最大元素

ST表状态转移

我们采用分治的思想,如果要查询一个区间的最大值,将区间一分为二,那么最大值要么存在于左半边,要么存在于右半部,结果取最大即可

同样,假设我们已经求出 所有的 【以 i 下标为区间左端点,包含 2j-1 个元素的区间的最大元素的值】,那么我们可以将问题合并了:

因为求出 2j-1 长度的区间了,那么合并后的区间长度就是 2j ,很容易推出状态转移:(假设是求最大值)

dp[i][j] = max(dp[i][j-1], dp[i+2^(j-1)][j-1])

左边区间为【l 为左端点,长度为 2j-1
右边区间为【l+2j-1 为左端点,长度为 2j-1

因为 2j = 2j-1 + 2j-1 ,使得两个子问题刚好覆盖原问题,这也是为什么我们要使用2的幂次作为长度的迭代单位的原因

ST表查询

我们只知道任意点起始,长度为2的幂次的区间的最值,但是如果输入的区间长度不是2的幂次怎么办?

但是没有关西 一个任意长度的区间,总可以分解为两个长度为2的幂方的子区间(即使两个子区间有些许重叠,但是重叠并不影响最终问题的解)

  • 假设长度是len=14,那么最大的幂方长度就是 sublen = 2 ^ log(2, len) = 8
  • 左子区间是 【左端点,,sublen-1】,答案1 就是 dp[left][log(2, len)]
  • 右子区间是【右端点-sublen+1,右端点】,答案就是 dp[right-sublen+1][sublen]

在这里插入图片描述

int rmq(int l, int r)
{
	if(l==r) return dp[l][0];
	int len=(r-l+1), lg=log2(len), sublen=pow2(lg);
	return max(dp[l][lg], dp[r-sublen+1][lg]);
}

代码

#include <bits/stdc++.h>

using namespace std;

#define maxn 1000
int dp[maxn][20], a[maxn], n;

int pow2(int x) {return 1<<x;}
int log2(int x) {return log((double)x)/log(2.0);}

int rmq(int l, int r)
{
	if(l==r) return dp[l][0];
	int len=(r-l+1), lg=log2(len), sublen=pow2(lg);
	return max(dp[l][lg], dp[r-sublen+1][lg]);
}

int main()
{	
	// 读取数据,dp数组初始化,长度为 2^0=1 的区间最大值就是单个元素 
	cin>>n;
	for(int i=0; i<n; i++) 
		cin>>a[i], dp[i][0]=a[i];
		
	// dp递推,因为dp[i][j] 基于所有的dp[][j-1],所以外循环是j递增 
	for(int j=1; pow2(j)<=n; j++)
		for(int i=0; i+pow2(j-1)<n; i++) 
			dp[i][j] = max(dp[i][j-1], dp[i+pow2(j-1)][j-1]);
	
	// 暴力法,RMQ查询,验证正确性 
	for(int i=0; i<n; i++)
		for(int j=i; j<n; j++)
			cout<<"["<<i<<","<<j<<"] "<<a[max_element(a+i, a+j+1)-a]<<" "<<rmq(i, j)<<endl;
	
	return 0;
}

/*
7
0 4 7 1 3 2 8
*/

输出:

7
0 4 7 1 3 2 8
[0,0] 0 0
[0,1] 4 4
[0,2] 7 7
[0,3] 7 7
[0,4] 7 7
[0,5] 7 7
[0,6] 8 8
[1,1] 4 4
[1,2] 7 7
[1,3] 7 7
[1,4] 7 7
[1,5] 7 7
[1,6] 8 8
[2,2] 7 7
[2,3] 7 7
[2,4] 7 7
[2,5] 7 7
[2,6] 8 8
[3,3] 1 1
[3,4] 3 3
[3,5] 3 3
[3,6] 8 8
[4,4] 3 3
[4,5] 3 3
[4,6] 8 8
[5,5] 2 2
[5,6] 8 8
[6,6] 8 8
发布了262 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44176696/article/details/105178155