RMQ(range maximum/minimum query)算法

1、应用背景
RMQ(range maximum/minimum query)算法是为了解决在一个给定的数组里面,随机的查询某一个区间中的最大或者最小值。如果只是少量的查询,直接遍历数组就可以了,当这种查询频率很高时,这样频繁的遍历数组显然是不可行的,这就需要用到这个算法。一个原则,当我们需要减少程序运行的时间,要么就是优化代码,要么就是用时间换空间。前者也就是部分的减少时间,或者说效果不是那么显著。而后者,如果设计得好,时间可以大大减少。后者的思想也就是我们熟知的动态规划的思想。所以,一切说得很玄乎玄乎的算法,扒光之后,都是从最几个最基本的思想出发的。
2、动态规划的递推求解
动态规划,无非就是用一些数组来保存中间的结果,需要用的时候,可以在O(1)的时间内得到。OK,涉及到动态规划,就不可避免的需要得到递推式。下面来层层解析。以随机求最大值为例,假设数组是{1,2,6,9,3},假设我们现在下标从1开始(为什么?一般的查询区间是从1开始的,当然如果实际应用是查询区间从0开始,做相应的更改就可以了。用一个dpMax[j][i],表示第i个数据开始,长度为2^j的数据段中的最大值。比如说dpMax[1][0],即从1开始,长度为2^0=1的最大值,就是1本身。dpMax[1][1] = max{1,2} = 2,dpMax[1][2] = max{1,2,6,9} = 3。OK,那么任给一个dpMax[i][j],那一定是前2^j-1个数的最大值和后2^(j-1)个数的最大值中较大的一个,即dpMax[i][j] = max(dpMax[i][j-1],dpMax[i+2^(j-1)][j-1])。OK,递推式出来之后,就好处理啦~初始值,直接等于数组中的元素就可以啦~也就是长度为1的情况啦~

void RMQ(int n)
{
	int k = GetBit(n);
	memcpy(&dpMax[0][1], &arr[1], n * sizeof(int));
	memcpy(&dpMin[0][1], &arr[1], n * sizeof(int));
	for(int j = 1; j <= k; ++j)//步长
	{
		for(int i = 1; i <= n; ++i)
		{
			if(i + (1 << j) - 1 <= n)
			{
				dpMax[j][i] = max(dpMax[j - 1][i], dpMax[j - 1][i + (1 << (j - 1))]);
				dpMin[j][i] = min(dpMin[j - 1][i], dpMin[j - 1][i + (1 << (j - 1))]);
			}
		}
	}
}

3、不是2的方幂怎么处理?
但是我们发现这里数组长度是5,不是2的方幂耶~那怎么办呢?假设数组的长度是n,那么就求出它的二进制最左边的一个1所代表的数(怎么求见GetBit)。继而我们发现我们要是求开始点b到结束点e的最大值,可能长度nLen = (e-b+1)也不是2的方幂,即不可能在我们的数组中直接有,没关系,根据我们刚刚求解递推式时候的思想,先求得k = log2(nLen),那么要求的最大值不就是前2^k个数的最大值和后2^k个数的最大值中较大者。这里可能两部分数据会有重复,那又怎么样,我们求最大值而已,一点都不会有影响。现在明白如果数组长度不是2的方幂怎么办了吧?凉拌,直接不管呗~~

int GetBit(unsigned int x)
{//最高位的1的位置
	int count = -1;
	while(x)
	{
		x >>= 1;
		++count;
	}
	return count;
}

 一个实际的题目应用:NYOJ119 士兵杀敌(三)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值