基于ST的RMQ的实现

1.RMQ:Range Minimum/Maximum Query。区间最值的询问。是一种基于数组上的操作,比如对于数组a,询问在区间(l,r)里面a的最大值。

2.RMQ问题有很多解决办法,比如直接暴力,线段树/树状数组,ST等。在这里面ST(sparse table:也被说成跳表)。在区间不做修改的时候是一种很好的解决办法,平均时间复杂度是O(nlogn),而且比较轻便,比线段树这些数据结构需要的空间少很多。

3.ST的基本思想:ST的基础是询问的答案具有这样的特征:区间可加性。即:对于一个问题,如果我们把问题的区间二分,我们可以把左区间的答案求出来,右区间的答案求出来,二者结合起来就可以求出来问题的答案。这种问题才适合。比如最大最小值/区间gcd等。我们以区间最大值来学习ST。我们知道整个区间的最大值等于左右区间最值较大的那个。那么我们可以把区间二分:在左边求一个最值,右边求一个,然后求最大。这样看起来很简单。ST的思路基本上也就是这个。在ST中,我们有一个DP数组,这个DP的意义就是:DP[m][n],以m为起点,长度为pow(2,n)这段区间的最大值。根据我们上边二分的思想:长度为pow(2,n)的区间可以一分为二,左边是m到pow(2,n-1)-1也就是:DP[m,m-1],右边是m+pow(2,n-1)到pow(2,n)-1,也就是:DP[m+(1<<n-1)][n-1]。这样转移式就求出来了。然后我们初始化:显然,DP[i][0]=a[i].这一步也完成了。然后是边界的控制:这个也不难,只要不超过我们数组的大小即可。但是,这里面有一个很重要的问题,显然我们这个是二维的DP,肯定是有两层循环,我们先枚举谁?答案是j,为什么,我们需要好好理解这个DP的意思,他是一种类似于扩散的东西,我们首先需要枚举这个扩散的半径,每个数据从自己向后扩散。这下问题基本上解决。询问就简单了,直接上代码了。

ll dp[maxn][20];
ll f[maxn];

void ST()
{
	int i, j, k;
	for (i = 1; i <= maxn; i++)
		dp[i][0] = f[i];
	k = log((double)(maxn + 1)) / log(2.0);
	for (j = 1; j <= k; j++)
		for (i = 1; i + (1 << j) - 1 <= maxn; i++)
			dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
int rmq_max(int l, int r)
{
	if (l>r)
		return 0;
	int k = log((double)(r - l + 1)) / log(2.0);
	return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}

4.POJ3368。这是一个利用ST解决RMQ问题的裸题,我们但是我们需要自己加一下预处理的信息。这个题的意思就是给一段区间,找出这段区间里面出现次数最多的那个数。注意,这里有一个很重要的信息,这些数据是单调的,也就说相同的数据都会在一团。具有空间局部性。我们怎么利用ST呢?我们首先用一个数组f来预处理输入的信息,f[i]表示从数组开始:a[0]到当前这个位置与a[i]相同的数据的个数,我们知道,这些数据肯定都在i的附件,有一个半径的样子,这样我们就可以使用ST了。然后我们来考虑区间的问题,对于区间[l,r],我们在使用ST求出f数组在这个区间的最值,不一定是我们想要的,因为左区间的问题,你不知道l左边的信息,那么我们要怎么办呢》难道还要特判l的左边吗?这显然是不可行的。既然左端点会出现问题,那么我们就把区间缩小,我们特判l附近的区间,但是是向右。我们特判掉这一段,然后剩下的使用ST求出最值,结果就是我们特判的结果和我们ST得到的结果的较大的那个。那么这个问题已经解决。贴上代码:(ps:这份代码只过了样例,因为POJ今天到现在不评测,先贴上,思路肯定是对的,要是WA了我后边再改)

#include<iostream>
#include<cstdio>
#include<stdio.h>
#include<cmath>
#include<iomanip>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn = 1e5 + 10;
ll a[maxn];
ll dp[maxn][20];
ll f[maxn];

void ST()
{
	int i, j, k;
	for (i = 1; i <= maxn; i++)
		dp[i][0] = f[i];
	k = log((double)(maxn + 1)) / log(2.0);
	for (j = 1; j <= k; j++)
		for (i = 1; i + (1 << j) - 1 <= maxn; i++)
			dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
int rmq_max(int l, int r)
{
	if (l>r)
		return 0;
	int k = log((double)(r - l + 1)) / log(2.0);
	return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}
#pragma warning(disable:4996)
int main()
{
	int n, q;
	while (~scanf("%d", &n)&&n)
	{
		scanf("%d", &q);
		for (int i =1; i <=n; i++)
		{
			scanf("%d", &a[i]);
			if (i == 1)
			{
				f[i] = 1;
				continue;
			}
			if (a[i]==a[i-1])
			{
				f[i] = f[i - 1] + 1;
			}
			else
			{
				f[i] = 1;
			}
		}
		ST();
		int l;
		int r;
		for (int i = 0; i < q; i++)
		{
			scanf("%d%d", &l, &r);
			int t = l;
			while (a[t] == a[t - 1] && t <= r)
				t++;
			int cnt = rmq_max(t, r);
			int ans = max(t - l, cnt);
			printf("%d\n", ans);
		}
	}
    return 0;
}

后记:sorry,之前的代码CE了,尴尬。。。。POJ还不能用万能头。。。。。加上C语言的东西就可以了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值