一、题意
输入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造成的误差问题 }
- 当题目与区间相关时,最好统一好使用左闭右开区间来表示,或者统一左闭右闭,否则容易出错