B - Frequent values (RMQ)UVA - 11235

原题地址

题意:

给出一组长度为N数,该数列非递减,然后给出q个区间对于每个区间,求出其中的众数的个数,就是出现次数最多的数的个数。

思路:

因为数据范围大,暴力会超时,需要特殊的算法,我们可以把这些数分为一段一段的,每一段的数是相同的,用cnt[i]表示第i个段对应的长度,即里面相同的数的数量。

我们可以在输入数列a[i]时把每个数都划到一个个段中,每个段中的值都是相同的,这样每个段都有个段号用num[i]保存,表示下标为i的数在第num[i]段中。

因为不知道所要求的区间(L,R)中有几个段,所以可以把结果分为三个部分,对这三个部分取最大值就是最终答案。第一个部分为从L开始的L对应的下标到L所在的段的右端点的长度,每个段的右端点可以用数组r[i]表示第i段的右端点为下标i,;第二部分为结尾R之前到R所在段的左端点的长度,每个段的左端点的下标用 l [i] 表示第i段的左端点的下标为 i;第三部分为除了这两段的中间部分,需要用到RMQ算法来求对cnt[i]来求区间最大值

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
#include<queue>
#include<stack>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int inf = 1 << 30;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int maxn = 1e5 + 10;
int cnt[maxn];//每个重复段对应的长度
int l[maxn];//每个段左端点对应的下标
int r[maxn];//每个段右端点对应的下标
int num[maxn];//i所属的段落号;
int a[maxn];//输入数组
int dp[maxn][30];//长度最长为1e5
void RMQ_Init(int t) //预处理RMQ
{
    int n = t;
    for (int i = 0; i < n; i++)
        dp[i][0] = cnt[i];//给dp[][]赋初值
    for (int j = 1; (1 << j) <= n; j++)//最大区间长度要小于n。
    {
        for (int i = 0; i + (1 << j) - 1 < n; i++)//二分思想,每次左半区和右半区比较。
            dp[i][j] = max(dp[i][j-1],dp[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(dp[L][k],dp[R-(1<<k)+1][k]);
}
int main()
{
    int n,q;
    while(cin>>n&&n)
    {
        cin>>q;
        for (int i = 1; i <= n; i++)
            cin>>a[i];
        memset(cnt,0,sizeof(cnt));
        memset(l,0,sizeof(l));
        memset(r,0,sizeof(r));
        int t = 0, tmp = 1;
        for (int i = 2; i < n; i++) //预处理确定每个数属于第几段,处理第2个到倒数第二个。
        {
            if (a[i] != a[i - 1]) //当两个值不相等时,段号加一
            {
                cnt[t] = tmp;//第t段重复的数的数量。
                r[t] = i - 1;//当前段的右端点下标
                t++;//段的标号
                num[i] = t;//当前位置的数所属段的标号
                l[t] = i;//下个段的左端点的下标
                tmp = 1;//新的一段重复数置为1.
            }
            else
            {
                tmp++;
                num[i] = t;
            }
        }
        r[t] = n;//最后一段的右端点下标就是n-1。
        cnt[t] = tmp;//最后一段的重复数个数就是tmp。
        num[n] = t;//最后一个数属于第t段。
        RMQ_Init(t);//RMQ预处理
        while(q--)
        {
            int x,y;
            cin>>x>>y;
            int L = num[x], R = num[y];//得出输入区间的端点分别是第几段的。
            if (L == R) //如果区间是在同一段直接得出结果。
            {
                cout<<y-x+1<<endl;
                continue;
            }
            int Max = 0;
            Max = max(r[L]-x+1,y-l[R]+1);//输入区间从左边开始的第一个重复段落的长度和从右边开始第一个重复段落的长度作比较。
            if (R -1 >= L+1)//如果中间还有除了L和R的其他段落,用RMQ求出其中最大的。
                Max = max(Max,RMQ(L+1,R-1));
            cout<<Max<<endl;
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhouchenghao123/article/details/84386240