Frequent Values(UVa 11235) RMQ问题

来自《算法竞赛入门经典训练指南》

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;
}
    原文作者:游程编码问题
    原文地址: https://blog.csdn.net/qq_33929112/article/details/52770300
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞