1. 程式人生 > >B - Frequent values (RMQ)UVA - 11235

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;
}