来自《算法竞赛入门经典训练指南》
1.RMQ问题
范围最小值问题(Range Minimum Query,RMQ)。给出一个n个元素的数组a[1],a[2],……a[n]。设计一个数据结构,支持查询操作Query(L,R):计算min{a[L],a[L+1],……a[R]}. 采用Tarjan的Sparse-Table算法,它预处理的时间是O(nlogn)。查询只需要O(1)。 令d[i][j]表示从i开始的长度为2^j的一段元素的最小值,则可以利用递推的方法计算d[i][j]=min{d[i][j-1],d(i+2^(j-1),j-1)}。 注意到2^j<=n,因此d数组的元素个数不超过nlogn。每一项都可以在常数时间内计算完毕,故总时间是O(nlogn)。 代码如下:
//d[i][j]表示从i开始的,长度为2^j的一段元素中的最小值
void RMQ_init(const vector<int> &A)
{
int n=A.size();
for(int i=0;i<n;i++){
d[i][0]=A[i];
}
for(int j=1;(1<<j)<=n;j++){
for(int i=0;i+(1<<j)-1<n;i++){
d[i][j]=min(d[i][j-1],d[i+(1<<(j-1))][j-1]);
}
}
}
查询操作很简单。令k是满足2^k<=R-L+1的最大整数,则以L开头、以R结尾的两个长度为2^k的区间合起来即覆盖了区间[L,R]。由于是取最小值,有些元素重复考虑,无关紧要。 查询代码如下:
int RMQ(int L,int R)
{
int k=0;
while((1<<(k+1))<=(R-L+1)) k++;
return min(d[L][k],d[R-(1<<k)+1][k]);
}
2.题目原文
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2176
给出一个非降序排列的整数数组a[1],a[2],……a[n]。你的任务是对于一系列查询(i,j),回答a[i],a[i+1],……a[j]中出现次数最多的值所出现的次数。
3.解题思路
由于是非降序排列,所以相等的数会聚集在一起,这样就可以对整个数组进行游程编码(Run Lenght Encoding,RLE)。比如-1,1,1,2,2,2,4可以编码为(-1,1),(1,2),(2,3),(4,1),其中(a,b)表示有b个连续的a。用value[i]和count[i]分别表示第i段的数值和出现次数,num[p],Left[p],Right[p]分别表示位置p所在段的编号和左右端点位置。每次查询(L,R)的结果是以下3部分的最大值:①从L到L所在段的结束处的元素个数(Right[L]-L+1);②从R所在段的开始处到R处的元素个数(R-Left[R]+1);③中间第num[L]+1段到第num[R]-1段的count的最大值。就可以转化为RMQ问题 注:RMQ是求解区间最小值的方法,将最小值改为最大值即可解决本问题。我认为该问题的难点是游程编码,想不到,书上给了方法还是不能用代码实现。
4.AC代码
#include<algorithm>
#include<cctype>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<set>
#include<vector>
#include<cmath>
#include<bitset>
#include<stack>
#include<sstream>
#include<deque>
#include<utility>
using namespace std;
#define INF 0x7fffffff
#define maxn 100005
#define maxlog 20
int d[maxn][maxlog];
//d[i][j]表示从i开始的,长度为2^j的一段元素中的最小值
void RMQ_init(const vector<int> &A)
{
int n=A.size();
for(int i=0;i<n;i++){
d[i][0]=A[i];
}
for(int j=1;(1<<j)<=n;j++){
for(int i=0;i+(1<<j)-1<n;i++){
d[i][j]=max(d[i][j-1],d[i+(1<<(j-1))][j-1]);
}
}
}
int RMQ(int L,int R)
{
int k=0;
while((1<<(k+1))<=(R-L+1)) k++;
return max(d[L][k],d[R-(1<<k)+1][k]);
}
int n,q;
int a[maxn];
int num[maxn],Left[maxn],Right[maxn];
//num[p]表示位置p所在段的编号
//Left[p],Right[p]分别表示位置p所在段的左右端点位置
int main()
{
while(scanf("%d",&n)!=EOF&&n){
scanf("%d",&q);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
a[n]=a[n-1]+1;//哨兵
int start=-1;
vector<int> count;
for(int i=0;i<=n;i++){
if(i==0||a[i]>a[i-1]){
//新一段开始
if(i>0){
count.push_back(i-start);
for(int j=start;j<i;j++){
num[j]=count.size()-1;
Left[j]=start;
Right[j]=i-1;
}
}
start=i;
}
}
RMQ_init(count);
int ans=0;
for(int i=0;i<q;i++){
int l,r;
scanf("%d%d",&l,&r);
l--;
r--;
if(num[l]==num[r]){
ans=r-l+1;
}
else{
ans=max(Right[l]-l+1,r-Left[r]+1);
if(num[l]+1<num[r]){
ans=max(ans,RMQ(num[l]+1,num[r]-1));
}
}
printf("%d\n",ans);
}
}
return 0;
}