范围最小值问题(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;
}