利用ST (Sparse - Table)算法求解区间最大/小值(RMQ)问题(以nyoj119-士兵杀敌(三)为例)

问题介绍:

RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列a,回答若干询问RMQ(A,i,j)(i, j<=n),返回数列a中下标在i,j之间的最小/大值。 

如果只有一次询问,那样只有一遍for就可以搞定,但是如果有许多次询问就无法在很快的时间处理出来。这里最简单的做法是采用暴力搜索,依次搜索给定区间中的元素,找出最值,当查询次数较多时,此方法效率较低。虽然可以采用记忆化搜索,但是当区间范围较大时,需要开辟一个很大的数组,浪费空间。因此常规方法在性能要求高的场景不适用。除此以外,还可以用线段树解决。

这里介绍了一种比较高效的在线算法(ST算法)解决这个问题。

问题解决:ST(Sparse Table)算法

ST(Sparse Table)是一个非常有名的在线处理RMQ问题的算法,该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。ST算法只适用于静态区间求最值,如果是动态的区间,就需要用线段树了。其算法本质为动态规划,我们用dp[i][j] 表示以 i 为起点,连续 2^j 个数中的最大值(最小值),例如 f [ 2 ][ 2 ] 就表示第 2 个数到第 5 个数的最大值(最小值)。

(1)预处理:dp[i][0]表示从下标i开始,连续2^0=1个数中的最大值(最小值),也就是a[i]。对于每一个 dp数组表示的序列,我们都把它拆成两部分,那么转移方程就是:dp [ i ][ j ] = max { dp [ i ][ j - 1 ] , dp [ i + 2^( j - 1 ) ][ j - 1 ] }     这样可以使常规动态规划预处理(区间dp,用dp [ i ][ j ] 表示以i为起点,j为终点的序列中最大值(最小值))的O(n^2)时间复杂度降为O(nlogn)

注:i + 2^( j - 1 )可以写成位运算的形式i+(1<<(j-1)) 注意括号,这里位运算的默认优先级最低

(2)查询:这里涉及到区间覆盖问题,假如我们需要查询的区间为(i,j),那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂(可以重复,比如查询5,6,7,8,9,我们可以查询5678和6789)。这个区间的长度为j - i + 1,所以我们可以取k=log2( j - i + 1),则有:RMQ(A, i, j)=max{dp[i][k], dp[ j - 2 ^ k + 1][k]}。 查询操作可以在O(1)时间复杂度内完成。

问题举例

nyoj 119-士兵杀敌(三)就是一道典型RMQ问题。

http://nyoj.top/problem/119 

代码:

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int T = 100005;
//dp[i][j]=max(dp[i][j-1],max[i+2^(j-1)][j-1])
int dpmax[T][21], dpmin[T][21];

int main()
{
	//freopen("debug.txt", "r", stdin);
	int n, q,beg,end,i,j,tmax,tmin,dis;
	scanf("%d%d", &n, &q);
	for (i = 1; i <= n; i++)
	{
		scanf("%d", &dpmax[i][0]);
		dpmin[i][0] = dpmax[i][0];
	}
	//初始化
	//for(j=1;(1<<j)<=n;j++)//O(nlogn)
	 for (j = 1; j<20; j++)
		for (i = 1; i + (1 << j) - 1 <= n; i++)
		{
			dpmax[i][j] = max(dpmax[i][j - 1], dpmax[i + (1 << (j - 1))][j - 1]);
			dpmin[i][j] = min(dpmin[i][j - 1], dpmin[i + (1 << (j - 1))][j - 1]);
		}
	while (q--)
	{
		scanf("%d%d", &beg, &end);
		dis = floor(log2(end - beg+1));
		tmax = max(dpmax[beg][dis], dpmax[end - (1 << dis)+1][dis]);
		tmin= min(dpmin[beg][dis], dpmin[end -( 1 << dis) + 1][dis]);
		printf("%d\n", tmax - tmin);
	}
	return 0;
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值