ST算法解决RMQ问题及UVA 11235题解

   范围最小值问题(Range Minimum Query,RMQ)。给出一个n个元素的数组A1,A2,……An,设计一个数据结构,支持查询操作Query(L,R):计算min{AL,AL+1,……,AR}。

http://blog.csdn.net/alongela/article/details/8143016

   每次用一个循环来计算最小值肯定很慢,实践中常用的的Sparse-Table算法(简称ST算法),朴素的方式是扫描起点到终点的所有数,维护其中的最值,这样的复杂度是O(n^2)的,速度太慢。ST算法是使用的是类似于二分的动态规划思想,其复杂度是O(nlogn),因此查询速度非常快。

1、初始化:

设原数组为x[N]。

开辟一个数组dp[N][33]。其中dp[i][j]表示的是从下标为i的元素开始,到下标为(i + 2^j - 1)的元素为止,这些元素中的最大值。对于整型而言,其值不会超过2^32,因此第二维大小为33已经足够。

因此dp[i][0]表示的是元素本身,因此可以初始化为dp[i][0] = x[i]。

对于其他的dp[i][j],可以采用动态规划的方式求出,递推式为dp[i][j] = max(dp[i][j - 1], dp[i + 2 ^ (j - 1)][j - 1]),其实就是把一段区间切成两段大小相等的区间,当前区间的最大值就是两个子区间的最大值中的较大者。

初始化的复杂度为O(nlogn)。

2、求解:

对于给定的起点beg及终点end,可以得出区间大小为range = end - beg + 1。

因此可以找到一个整数k = (int)(log(range) / log2)。这样区间就可以被划分为子区间1,即[beg, beg + (2 ^ k) - 1],子区间2,即[end - (2 ^ k) + 1, end]。这两个可能会有重叠,但重叠不会影响最大值的求解。因此对于beg和end,可以得到解为res = max(dp[beg][k], dp[end - (2 ^ k) + 1][k])。

求解的复杂度为O(1)。

值得注意的是使用log求解k的速度比较慢,可以使用乘法来计算k,这样速度会相对快一些。

具体方法是:

k = 0, x = 2, range = end - beg + 1;

while (x <= range)

{

k++;

x <<= 1;


对于某个RMQ问题,总的复杂度为O(nlogn) + n * O(1) = O(nlogn),因此可以在足够快的时间内得到区间的最大值或最小值。

下面来说说这道题

http://poj.org/problem?id=3368


11235 - Frequent values

Description

You are given a sequence of n integers a1 , a2 , ... , an in non-decreasing order. In addition to that, you are given several queries consisting of indices i and j (1 ≤ i ≤ j ≤ n). For each query, determine the most frequent value among the integers ai , ... , aj.

Input

The input consists of several test cases. Each test case starts with a line containing two integers n and q (1 ≤ n, q ≤ 100000). The next line contains n integers a1 , ... , an (-100000 ≤ ai ≤ 100000, for each i ∈ {1, ..., n}) separated by spaces. You can assume that for each i ∈ {1, ..., n-1}: ai ≤ ai+1. The following q lines contain one query each, consisting of two integers i and j (1 ≤ i ≤ j ≤ n), which indicate the boundary indices for the 
query.

The last test case is followed by a line containing a single 0.

Output

For each query, print one line with one integer: The number of occurrences of the most frequent value within the given range.

Sample Input

10 3
-1 -1 1 1 1 1 3 10 10 10
2 3
1 10
5 10
0

Sample Output

1
4
3
题目的意思就是给出一个非降序的整数数组 a 1, a 2,..... a n,你的任务是对于一系列询问(i,j),回答 a i , a i+1 ,..... a j中出现次数最多的值所出现的次数

【分析】

     应注意到整个数组时非降序的,所有相等元素都会聚集到一起。这样就可以把整个数组进行游程编码(Run Length Encoding,RLE)。比如 -1 1 1 2 2 2 4就可以编码成(-1,1),(1,2),(2,3),(4,1),其中(a,b)表示有b个连续的a。用count[i]表示第i段的出现次数,num[p]、left[p]、right[p]分别表示位置p所在段的编号和左右端点位置,则每次查询(L,R)的结果为以下3个部分的最大值:从L到L所在段的结束处的元素个数(right[num[L]]-L+1)、从R所在段的开始处到R处的元素个数(R-left[num[R]]+1)、中间第num[L]+1段到第num[R]-1段的count的最大值。

这道题目我一开始很困惑,我大概知道思路后开始自己动手写,写完后答案是正确的,但是提交却是超时,但是我明明也是用的ST算法,这让我百思不得其解,后来看了别人过了的代码对比了以下,知道了原因所在,就是RLE处理那一块,我没有用到left,right还有num去保存那些数据,我是在输入了R和L后再去处理RLE,最终结果也就是RMQ(1,L);这样的做法是没有错的,但是会超时,因为每输入一次就要RLE一次,下面贴上我写的两份代码,第一份是超时的,第二份是AC的

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 100001
int n,q,a[N],L,R;
int count[N],value[N];
int d[N][33]={0};
int l;//段数
//游程编码
int max(int a,int b)
{
	if(a>=b) return a;
	else return b;
}
int RLE(int beg,int end)
{
	int j=1;//段数
	for(int i=0;i<=n;i++)
	count[i]=1;
	for(int i=beg;i<end;i++){
		if(a[i]==a[i+1]){
			if(i==end-1){
				value[j]=a[i];	
			}
			count[j]++;
		}else{
			value[j]=a[i];
			j++;
			if(i==end-1){
				value[j]=a[end];
			}
		}
	}
	return j;
}
void Init()
{
	memset(a,0,sizeof(a));
	memset(value,0,sizeof(value));
}
void RMQ_init()
{
	memset(d,0,sizeof(d));
	for(int i=1;i<=l;i++) d[i][0]=count[i];
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=l;i++)
			d[i][j]=max(d[i][j-1],d[i+(1<<(j-1))][j-1]);
}
int RMQ(int l,int r)
{
	if(l > r)
        return 0;
	int k=0;
	while(1<<(k+1)<=r-l+1) k++;
	return max(d[l][k],d[r-(1<<k)+1][k]);
}
int main()
{
	while(~scanf("%d",&n)){
		if(n==0) break;
		scanf("%d",&q);
		Init();
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		for(int i=0;i<q;i++){
			scanf("%d%d",&L,&R);
			l=RLE(L,R);
			RMQ_init();
			if(l==1) printf("%d\n",R-L+1);
			else{
				printf("%d\n",RMQ(1,l));
			}
		}
	} 	
	return 0;
}
/*
10 3
-1 -1 1 1 1 1 3 10 10 10
2 3
1 10
5 10
0
*/


 
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 100000+10
int num[N],right[N],left[N];
int d[N][33];
int n,q,a[N];
int count[N];
int l;
int max(int a,int b)
{
	if(a>b) return a;
	return b;
}
void Init()
{
	memset(d,0,sizeof(d));
	memset(a,0,sizeof(a));
}
int RLE()
{
	int j=1;
	for(int i=0;i<=n;i++) count[i]=1;
	memset(num,0,sizeof(num));
	memset(left,0,sizeof(left));
	memset(right,0,sizeof(right));
	for(int i=1;i<n;i++){
		num[i]=j;
		if(i==1) left[j]=right[j]=1;
		if(a[i]==a[i+1]){
			if(i==n-1) num[i+1]=j;
			count[j]++;
			right[j]++;
		}else{
			j++;
			if(i==n-1) num[i+1]=j;
			left[j]=right[j]=i+1;
		}
	}
	return j;
}
void RMQ_init()
{
	memset(d,0,sizeof(d));
	for(int i=1;i<=l;i++) d[i][0]=count[i];
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=l;i++)
			d[i][j]=max(d[i][j-1],d[i+(1<<j-1)][j-1]);
}
int RMQ(int l,int r)
{
	if(l > r)
        return 0;
	int k=0;
	while(1<<(k+1)<=r-l+1) k++;
	return max(d[l][k],d[r-(1<<k)+1][k]);
}
int main()
{
	int L,R;
	while(~scanf("%d%d",&n,&q)&&n){
		Init();
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
			l=RLE();
//			printf("%d\n",l);
//		for(int i=1;i<=l;i++)
//			printf("%d ",count[i]);
//		printf("\n");
//		for(int i=1;i<=l;i++)
//			printf("l[%d]=%d r[%d]=%d ",i,left[i],i,right[i]);
//		printf("\n");
//		for(int i=1;i<=n;i++)
//		printf("%d ",num[i]);
//		printf("\n");
		RMQ_init();
		for(int i=0;i<q;i++){
			scanf("%d%d",&L,&R);
			if(num[L] == num[R])
                printf("%d\n", R-L+1);
			else{
				//因为L和R那两段不一定是完整的两段,所以次数需要另算,不能直接传进RMQ 
				int tmp1=right[num[L]]-L+1;
				int tmp2=R-left[num[R]]+1;
				int tmp3=RMQ(num[L]+1,num[R]-1);
				printf("%d\n",max(tmp1,max(tmp2,tmp3)));
			}
		} 
	}
	return 0;
}


 



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值