LUOGU P4119 Ynoi2018 未来日记

更好的阅读体验

题意

有一个长度为 \(n\) 的序列,共 \(m\) 次操作:

  • 1 l r x y,把区间 \([l, r]\) 内所有 \(x\) 变成 \(y\)
  • 2 l r k,查询区间 \([l, r]\) 内第 \(k\) 小值.

\(1\le n, m\le 10^5\),任何时刻 \(1\le a_i\le 10^5\)

题解

对于这种复杂的修改操作,我们直接考虑分块

对序列和值域同时分块,令 \(sum_{i, v}\) 表示前 \(i\) 块内值 \(v\) 的个数,\(blsum_{i, j}\) 表示序列前 \(i\) 块内值在值域第 \(j\) 块内的个数

考虑查询,对于散块我们每次查询构造 \(sctsum_{v}\) 表示在散块中值为 \(v\) 的个数,\(sctblsum_{i}\) 表示散块中值在值域第 \(i\) 块内的个数

查询时我们对整块差分再加上散块的贡献,即可 \(\Theta(1)\) 地查询区间 \([l, r]\) 内值为 \(v\) 的个数和区间内值在值域第 \(i\) 块中的个数

先枚举 \(k\) 小值在值域中的哪一块,再枚举 \(k\) 小值是该块中的哪一个值,一次查询的时间复杂度为 \(\Theta(\sqrt{n})\)

考虑修改,对于散块直接暴力修改即可

对于每个整块独立地给块内每一个值一个标号,颜色相同的有相同的标号,还需要维护值对标号的映射和值对标号的映射

\(x\) 变成 \(y\) 时的两种情况:

  • 块内没有 \(y\)
    \(x\) 对应的标号映射到 \(y\),将 \(y\) 映射到原本 \(x\) 对应的标号,将 \(x\) 的映射清空
  • 块内有 \(y\)
    不太好处理,直接像对散块一样暴力修改
    考虑这样做的时间复杂度
    暴力修改的次数不多于所有块内不同值个数的和,一开始有 \(\mathcal{O}(n)\) 个,每修改一次整块不会增加,只有散块可能增加1,所以总共暴力修改 \(\mathcal{O}(n+m)\) 次,总的暴力修改复杂度即为 \(\mathcal{O}((n+m)\sqrt{n})\)

每次暴力修改前都要 "rebuild" 该块,即重新分配标号,重新生成两个映射
每次操作散块都要前 "reset" 该块,即对于每个值用标号映射得到新的值

总的时间复杂度为 \(\mathcal{O}((n+m)\sqrt{n})\)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100000, maxvs = 316;
const int maxs = 360, maxc = 278;
int cnt;
int a[maxn + 5];
int lp[maxc + 5], rp[maxvs + 5];
int bl[maxn + 5];
int vlp[maxvs + 5];
int vbl[maxn + 5];
int sum[maxc + 5][maxn + 5];
int blsum[maxc + 5][maxvs + 5];
int rep[maxc + 5][maxn + 5];
int col[maxc + 5][maxs + 5];
int id[maxn + 5];
int ccnt[maxc + 5];
void buildUnion(int bid) {
    for (int i = 1; i <= ccnt[bid]; i++)
        rep[bid][col[bid][i]] = 0;
    ccnt[bid] = 0;
    for (int i = lp[bid]; i <= rp[bid]; i++) {
        if (rep[bid][a[i]] == 0) {
            rep[bid][a[i]] = ++ccnt[bid];
            col[bid][ccnt[bid]] = a[i];
        }
        id[i] = rep[bid][a[i]];
    }
}
void reset(int bid) {
    for (int i = lp[bid]; i <= rp[bid]; i++)
        a[i] = col[bid][id[i]];
}
void build(int n) {
    int siz = min(n, int(sqrt(1.3 * n)));
    cnt = ceil(1. * n / siz);
    for (int i = 1; i <= n; i++)
        bl[i] = (i - 1) / siz + 1;
    for (int i = 1; i <= cnt; i++) {
        lp[i] = (i - 1) * siz + 1;
        rp[i] = min(n, i * siz);
    }
    for (int i = 1; i <= maxn; i++)
        vbl[i] = (i - 1) / maxvs + 1;
    for (int i = 1; i <= maxvs + 1; i++)
        vlp[i] = (i - 1) * maxvs + 1;
    for (int i = 1; i <= cnt; i++) {
        for (int j = 1; j <= maxn; j++)
            sum[i][j] = sum[i - 1][j];
        for (int j = 1; j <= maxvs + 1; j++)
            blsum[i][j] = blsum[i - 1][j];
        for (int j = lp[i]; j <= rp[i]; j++) {
            sum[i][a[j]]++;
            blsum[i][vbl[a[j]]]++;
        }
        buildUnion(i);
    }
}
int tmp[maxn + 5], bltmp[maxvs + 5];
void add(int l, int r, int delta) {
    for (int i = l; i <= r; i++) {
        tmp[a[i]] += delta;
        bltmp[vbl[a[i]]] += delta;
    }
}
int query(int l, int r, int k) {
    int lb = bl[l], rb = bl[r];
    int res;
    if (lb == rb) {
        reset(lb);
        copy(a + l, a + r + 1, tmp + l);
        nth_element(tmp + l, tmp + l + k - 1, tmp + r + 1);
        res = tmp[l + k - 1];
        fill(tmp + l, tmp + r + 1, 0);
    } else {
        reset(lb);
        reset(rb);
        add(l, rp[lb], 1);
        add(lp[rb], r, 1);
        int i = 1, delta;
        for (; k - (delta = bltmp[i] + blsum[rb - 1][i] - blsum[lb][i]) > 0;
             i++)
            k -= delta;
        int j = vlp[i];
        for (; k - (delta = tmp[j] + sum[rb - 1][j] - sum[lb][j]) > 0; j++)
            k -= delta;
        res = j;
        add(l, rp[lb], -1);
        add(lp[rb], r, -1);
    }
    return res;
}
void change(int l, int r, int x, int y, int bid) {
    int chcnt = 0;
    for (int i = l; i <= r; i++) {
        if (a[i] == x) {
            a[i] = y;
            chcnt++;
        }
    }
    sum[bid][x] -= chcnt;
    sum[bid][y] += chcnt;
    blsum[bid][vbl[x]] -= chcnt;
    blsum[bid][vbl[y]] += chcnt;
}
void changeBlock(int l, int r, int x, int y, int bid) {
    reset(bid);
    change(l, r, x, y, bid);
    buildUnion(bid);
}
void modify(int l, int r, int x, int y) {
    int lb = bl[l], rb = bl[r];
    if (x == y || sum[rb][x] - sum[lb - 1][x] == 0)
        return;
    for (int i = cnt; i >= lb; i--) {
        sum[i][x] -= sum[i - 1][x];
        sum[i][y] -= sum[i - 1][y];
        blsum[i][vbl[x]] -= blsum[i - 1][vbl[x]];
        blsum[i][vbl[y]] -= blsum[i - 1][vbl[y]];
    }
    if (lb == rb)
        changeBlock(l, r, x, y, lb);
    else {
        changeBlock(l, rp[lb], x, y, lb);
        changeBlock(lp[rb], r, x, y, rb);
        for (int i = lb + 1; i < rb; i++) {
            if (sum[i][x] == 0)
                continue;
            if (sum[i][y] == 0) {
                col[i][rep[i][x]] = y;
                swap(rep[i][y], rep[i][x]);
                blsum[i][vbl[y]] += sum[i][x];
                blsum[i][vbl[x]] -= sum[i][x];
                sum[i][y] = sum[i][x];
                sum[i][x] = 0;
            } else
                changeBlock(lp[i], rp[i], x, y, i);
        }
    }
    for (int i = lb; i <= cnt; i++) {
        sum[i][x] += sum[i - 1][x];
        sum[i][y] += sum[i - 1][y];
        blsum[i][vbl[x]] += blsum[i - 1][vbl[x]];
        blsum[i][vbl[y]] += blsum[i - 1][vbl[y]];
    }
}
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    build(n);
    for (int i = 1; i <= m; i++) {
        int op, l, r;
        scanf("%d%d%d", &op, &l, &r);
        if (op == 1) {
            int x, y;
            scanf("%d%d", &x, &y);
            modify(l, r, x, y);
        } else {
            int k;
            scanf("%d", &k);
            printf("%d\n", query(l, r, k));
        }
    }
}
posted @ 2022-02-02 18:09  gzezFISHER  阅读(48)  评论(0编辑  收藏  举报