一、题意

输入n、q(n、q<=100000),然后输入一个长度为n的递增序列A(有重复元素)。
之后是q次询问,每次询问输入两个数l、r,你需要输出在区间 A[l...r] 中出现次数最多的元素的出现次数。

二、解析

为了不超时,每次询问我们需要在O(1)~O(lgn)时间内作出回答。这就让我们想到有一个在O(1)时间内得到区间最值的算法:RMQ算法。

在本题中,就是要在一个cnt数组中得到某个区间中的cnt最大值。

因此我们很自然地需要对原序列进行统计,得到一个“把相同值聚类”后的新数组,为了方便起见,还需要维护每一个值在原数组的左边界和右边界,因此这里我用一个结构体来保存,即代码中的segment类。

由于题目询问时给出的是原数组的下标,因此我们还需要一个从原数组下面映射到新数组下标的映射数组seg_idx[maxn]。

之后求出现次数的最值就变得简单了,即最大值将会是以下3个值之一:

  • l到它所在的segment的右边界的长度
  • r到它所在的segment的左边界的长度
  • RMQ_cnt(segment[seg_idx[l] + 1], segment[seg_idx[r]]) ,即segmemt[l所在segment的下标+1, r所在segment的下标)

三、代码

#include <iostream>
#include <cmath>
using namespace std;
const int maxn = 100000 + 5, log2_maxn = (int)log2(maxn) + 5;
struct segment {
    int val, cnt, l, r;  // 左闭右闭
    segment() {}
    segment(int val, int cnt, int l, int r) : val(val), cnt(cnt), l(l), r(r) {}
} seg[maxn];
int n, q, a[maxn], seg_id[maxn], dp[maxn][log2_maxn];

void RMQ_init(int m) {
    for(int i = 0; i < m; i ++) dp[i][0] = seg[i].cnt;  // 左闭右开
    for(int j = 1; (1 << j) < m; j ++) {
        for(int i = 0; i + (1 << j) - 1 < m; i ++)
            dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j-1))][j - 1]);
    }
}

int RMQ_max(int l, int r) {  // 左闭右开
    if(l >= r) return 0;
    int k = log2(r - l);
    return max(dp[l][k], dp[r - (1 << k)][k]);
}

int main() {
    while(cin >> n && n) {
        cin >> q;
        for(int i = 0; i < n; i ++) cin >> a[i];

        int idx = 0;
        seg[0] = segment(a[0], 1, 0, 0), seg_id[0] = 0;
        for(int r = 1; r < n; r ++) {
            if(a[r] == seg[idx].val) seg[idx].cnt ++, seg[idx].r = r;
            else idx ++, seg[idx] = segment(a[r], 1, r, r);
            seg_id[r] = idx;
        }
        RMQ_init(idx + 1);

        while(q --) {
            int l, r, ans;
            cin >> l >> r;
            l --, r --;
            if(seg_id[l] == seg_id[r]) ans = r - l + 1;
            else ans = max(max(seg[seg_id[l]].r - l + 1, r - seg[seg_id[r]].l + 1),
                          RMQ_max(seg_id[l] + 1, seg_id[r]));
            cout << ans << endl;
        }
    }
}

四、归纳

  • RMQ算法:实现在O(1)时间内得到数组中某个下标区间内的最值,注意RMQ中的区间一般表示为左闭右开,即[l, r)。下面是以最大值为例的模板:
#include <cmath>

const int maxn = 100000 + 5, log2_maxn = (int)log2(maxn) + 5;
int a[maxn], dp[maxn][log2_maxn];

// 通过动态规划得到dp[i][j]数组,表示原数组a[maxn]中区间a[i, i + 2^j)中的最大值
void RMQ_init(int n) {
    for(int i = 0; i < n; i ++) dp[i][0] = a[i];  // 只有一个数的区间
    for(int j = 1; (1 << j) < n; j ++) {  // 大区间由小区间计算而成,因此从小到大枚举j
        for(int i = 0; i + (1 << j) - 1 < n; i ++)  // 循环边界条件:i + 2^j - 1为最后一个下标
            dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j-1))][j - 1]);
    }
}

int RMQ_max(int l, int r) {  // 左闭右开
    if(l >= r) return 0;
    int k = log2(r - l);
    return max(dp[l][k], dp[r - (1 << k)][k]);  // 通过取两个区间的最值来处理log造成的误差问题
}
  • 当题目与区间相关时,最好统一好使用左闭右开区间来表示,或者统一左闭右闭,否则容易出错