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;
}
posted @ 2024-02-03 22:35  BadBadBad__AK  阅读(28)  评论(0)    收藏  举报