AcWing 249. 蒲公英 题解
数字个数较少,但是数字取值范围非常大,所以需要先离散化。由于题目要求如果多个数同时是众数,则取最小的,所以这里需要做保序离散化。应对查询的算法是分块。先预处理两个二维数组,第一个是 \(s[i][x]\),其含义是前 \(i\) 个块里 \(x\) 的出现次数,这是为了快速算出连续若干个整块里 \(x\) 的出现次数,例如第 \(l\) 到第 \(r\) 个块里 \(x\) 的出现次数就是 \(s[r][x]−s[l−1][x]\);第二个是 \(f[i][j]\),代表第 i 个块到第 j 个块这一段区间的众数是多少。具体查询的时候,例如查区间 \([l,r]\) 的众数是多少,如果 \([l,r]\) 内不含整块,则直接暴力求解众数;否则先假设众数是中间的整块里的众数,可以查 \(f\) 得到,接着遍历散块里的每个数,验证这些数是否能成为众数。
#include <bits/stdc++.h>
#define l first
#define r second
using namespace std;
using PII = pair;
// T是分块数的上限
const int N = 40010, T = 210;
// b[i]是第i个块的左右端点下标,下标从1开始
PII b[T];
// n代表数字总个数,m代表询问个数,sz代表每个整块的数字个数,
// cnt代表块的个数(包括散块),len代表离散化之后得到的数字的最大数
int n, m, sz, cnt, len;
// lsh是a离散化之后得到的排序好的数,blk[i]是下标i所属的块的下标
int a[N], lsh[N], blk[N], t[N];
// s的第二维取的是离散化之后的值
int s[T][N], f[T][T];
int res;
// 返回x离散化之后的值
int getval(int x) {
return lower_bound(lsh + 1, lsh + 1 + len, x) - lsh;
}
void init() {
// 先求一下有多少个分块
cnt = n / sz;
if (n % sz) cnt++;
// 预处理b数组
for (int i = 1; i <= cnt; i++) b[i] = {(i - 1) * sz + 1, i * sz};
b[cnt].r = n;
// 预处理s数组
for (int i = 1; i <= cnt; i++) {
int l = b[i].l, r = b[i].r;
for (int j = l; j <= r; j++) s[i][a[j]]++, blk[j] = i;
for (int j = 1; j <= len; j++) s[i][j] += s[i - 1][j];
}
// 预处理f数组
for (int i = 1; i <= cnt; i++)
for (int j = i; j <= cnt; j++) {
// 每次先取i到j - 1这几个分块里的众数,然后看一下第j个分块里的数是否能成为新的众数
int mode = f[i][j - 1];
for (int k = b[j].l; k <= b[j].r; k++) {
int v1 = s[j][a[k]] - s[i - 1][a[k]], v2 = s[j][mode] - s[i - 1][mode];
// 如果a[k]出现次数更多,则众数更新为a[k],如果出现次数一样多,则更新众数为较小值
if (v1 > v2) mode = a[k];
else if (v1 == v2) mode = min(mode, a[k]);
}
f[i][j] = mode;
}
}
// 查询范围为[L, R]的情况下,用a[l : r]内的数来更新mode
int incomp_block(int l, int r, int L, int R, int mode) {
for (int i = l; i <= r; i++) {
int v1 = t[a[i]] + s[blk[R] - 1][a[i]] - s[blk[L]][a[i]];
int v2 = t[mode] + s[blk[R] - 1][mode] - s[blk[L]][mode];
if (v1 > v2) mode = a[i];
else if (v1 == v2) mode = min(mode, a[i]);
}
return mode;
}
// 如果查询区间在同一个分块里,直接暴力求众数。这个函数处理这种情况
int one_block(int l, int r) {
for (int i = l; i <= r; i++) t[a[i]]++;
int mode = a[l];
for (int i = l + 1; i <= r; i++)
if (t[a[i]] > t[mode]) mode = a[i];
else if (t[a[i]] == t[mode]) mode = min(mode, a[i]);
// 算完了将t清零
for (int i = l; i <= r; i++) t[a[i]] = 0;
return lsh[mode];
}
int solve(int l, int r) {
// 如果查询区间在同一个分块里,直接暴力求众数
if (blk[r] - blk[l] <= 1) return one_block(l, r);
// 收集一下散块里各个数的出现次数
for (int i = l; i <= b[blk[l]].r; i++) t[a[i]]++;
for (int i = b[blk[r]].l; i <= r; i++) t[a[i]]++;
// 直接查询整块部分的众数
int mode = f[blk[l] + 1][blk[r] - 1];
// 看一下散块里的数是否能成为新的众数
mode = incomp_block(l, b[blk[l]].r, l, r, mode);
mode = incomp_block(b[blk[r]].l, r, l, r, mode);
// 算完之后将t清零
for (int i = l; i <= b[blk[l]].r; i++) t[a[i]] = 0;
for (int i = b[blk[r]].l; i <= r; i++) t[a[i]] = 0;
return lsh[mode];
}
int main() {
scanf("%d%d", &n, &m);
sz = sqrt(n);
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
a[i] = lsh[i] = x;
}
// 做离散化
sort(lsh + 1, lsh + 1 + n);
len = unique(lsh + 1, lsh + 1 + n) - (lsh + 1);
// 将a算成离散化之后的值
for (int i = 1; i <= n; i++) a[i] = getval(a[i]);
init();
for (int i = 1; i <= m; i++) {
int ll, rr;
scanf("%d%d", &ll, &rr);
int l = (ll + res - 1) % n + 1, r = (rr + res - 1) % n + 1;
if (l > r) swap(l, r);
printf("%d\n", res = solve(l, r));
}
return 0;
}

浙公网安备 33010602011771号