Solution P4119

· · 题解

历经 3 天,拍了 70000^+ 组数据,我终于过了! 纪念一下。

给一个长度为 n 的序列 a,支持一下两种操作:

  1. 1 l r x y 将区间 [l,r] 中的 x 改为 y
  2. 2 l r k 查询区间第 k 小。

介绍一下,这种算法叫“望月悲叹的最初分块”,lxl 评分 8.5

首先考虑如何处理询问,有主席树等在线做法,当然也可以分块套值域树状数组(似乎分块套树状数组能做的值域分块都能做?),但这里使用值域分块。

我们希望将查询复杂度控制在 O(\sqrt n) 内,那我们需要快速知道第 k 小,在不在一个值域块中,那就是说,我们希望 O(1) 查询区间 [l,r] 中值域块 i 中的个数;而且具体到一个值域块内,我们要 O(1) 查询一个数在区间中出现的个数,自然考虑前缀和。

于是设 b_{i,j} 表示前 i 块,第 j 值域块的数的个数;c_{i,j} 表示前 i 块,数字 j 的个数,对于散块开两个桶暴力统计。

接下来,思考一下怎么修改。

对于边角块,直接暴力重构,复杂度 O(\sqrt n)

对于整块,分为以下三类:

  1. 不存在 x,这种块直接忽略。
  2. 存在 x,不存在 y,将 x 映射到 y 即可,具体来讲,我们见每个点拆成数 x 对应的值,和值为 x 对应的点两类。我们令 val_x 表示数 x 对应的真实值,pos_x 为值 x 对应的点,修改时直接令 val_{pos_x}=y,同时保证再次修改时从 y 更新到 a_i,将 pos_y \leftarrow pos_x
  3. 即存在 x 又存在 y,由于每次合并不同数字的个数减 1,所以这种合并最多经行 n 次,那就暴力重构就好了。

时间复杂度 O((n + m)\sqrt n),空间复杂度 O(n\sqrt n)

由于我的写法常数太大,只获得了 64pts,考虑优化:

  1. 首先注意直接省略 x=y 的询问,不然会全 TLE。
  2. 边角块修改同样可以应用 x 不存在就省略的策略。
  3. 在初始化的时候,可以每次记录前面的值域,前缀和可以只从这个范围更新。
  4. 查询后清空数组太慢了,用一个栈存一下要清空哪些数可以快很多。

优化后期望得分 100pts

#include <bits/stdc++.h>
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2")
using namespace std;

namespace IO{
    char ibuf[(1 << 20) + 1], *iS, *iT;
    #if ONLINE_JUDGE
    #define gh() (iS == iT ? iT = (iS = ibuf) + fread(ibuf, 1, (1 << 20) + 1, stdin), (iS == iT ? EOF : *iS++) : *iS++)
    #else
    #define gh() getchar()
    #endif
    #define reg register
    inline long long read () {
        reg char ch = gh();
        reg long long x = 0;
        reg char t = 0;
        while (ch < '0' || ch > '9') t |= ch == '-', ch = gh();
        while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = gh();
        return t ? -x : x;
    }
    inline void write(long long x) {
        if (x < 0) {
            x = ~(x - 1);
            putchar('-');
        }
        if (x > 9)
            write(x / 10);
        putchar(x % 10 + '0');
    }
}

using IO::read;
using IO::write;

const int maxn(1e5 + 50), maxk(325), sqv(316), lenv(317);
int n, T, sqn, len, a[maxn], id[maxn], ls[maxk], rs[maxk], b[maxk][maxk], c[maxk][maxn], cntx[maxk], cnty[maxk], cx[maxk], cy[maxk], tx[maxk], ty[maxn], idv[maxn], mx1, mx2;
int stk1[maxk << 1], top1, stk2[maxk << 1], top2;

struct Block {
    int pos[maxn], val[maxn], now;
    inline void build () {
        for (int i = 1; i <= mx1; i++) b[now][i] = b[now - 1][i];
        for (int i = 1; i <= mx2; i++) c[now][i] = c[now - 1][i];//继承值域的优化 
        for (int i = ls[now]; i <= rs[now]; i++) {
            id[i] = now;
            b[now][idv[a[i]]]++;
            c[now][a[i]]++;
            mx1 = max(mx1, idv[a[i]]);
            mx2 = max(mx2, a[i]);
            val[a[i]] = a[i];
            pos[a[i]] = a[i];
        }
    }//初始化 b,c 和 pos,val 
    inline void rebuild (int x, int y) {
        b[now][idv[x]] = b[now - 1][idv[x]];
        b[now][idv[y]] = b[now - 1][idv[y]];
        c[now][x] = c[now - 1][x];
        c[now][y] = c[now - 1][y];//重构的过程中同样注意到只有 x,y 的值会发生改变,保证复杂度为 O(sqrt(n)) 
        for (int i = ls[now]; i <= rs[now]; i++) {
            if (a[i] == x) c[now][x]++;
            else if (a[i] == y) c[now][y]++;
            if (idv[a[i]] == idv[x]) b[now][idv[x]]++;
            else if (idv[a[i]] == idv[y]) b[now][idv[y]]++;
            val[a[i]] = a[i];
            pos[a[i]] = a[i];
        }
    }
    inline void mdy (int x, int y) {
        val[pos[x]] = y;
        pos[y] = pos[x];
        pos[x] = 0;//将 x 映射到 y 
    }
    inline int reduct (int x) {
        return val[x];//x 的真实值 
    }
} G[maxk];

inline int qry (int k, int L, int R) {
    int pos = 1;
    while (k > (b[R][pos] - b[L - 1][pos] + tx[pos])) k -= (b[R][pos] - b[L - 1][pos] + tx[pos]), pos++;//先枚举在那个值域块 
    int i = (pos - 1) * sqv + 1;
    while (k > (c[R][i] - c[L - 1][i] + ty[i])) k -= (c[R][i] - c[L - 1][i] + ty[i]), i++;//枚举是值域块中的那个数 
    return i;
}

int main () {
    n = read(), T = read(), sqn = sqrt(n), len = (n - 1) / sqn + 1;
    for (int i = 1; i <= len; ++i) ls[i] = rs[i - 1] + 1, rs[i] = i * sqn; rs[len] = n;
    for (int i = 1; i <= 100000; ++i) idv[i] = (i - 1) / sqv + 1;
    for (int i = 1; i <= n; ++i) a[i] = read();
    for (int i = 1; i <= len; ++i) G[i].now = i, G[i].build();
    while (T--) {
        int opt = read(), l = read(), r = read();
        if (opt == 1) {
            int x = read(), y = read(), ll = id[l], rr = id[r];
            if (x == y) continue;//这里没写 0pts,调了一天…… 
            for (int i = ll + 1; i <= len; ++i) cntx[i] = c[i][x] - c[i - 1][x], cnty[i] = c[i][y] - c[i - 1][y], cx[i] = b[i][idv[x]] - b[i - 1][idv[x]], cy[i] = b[i][idv[y]] - b[i - 1][idv[y]];
            //cntx[i] 表示第 i 块中 x 的个数,cnty[i] 同理,cx[i] 表示第 i 块中 x 所在值域块的数的个数 
            if (ll == rr) {
                if (c[ll][x] == c[ll - 1][x]) continue;
                for (int i = ls[ll]; i <= rs[ll]; ++i) a[i] = G[ll].reduct(a[i]);
                for (int i = l; i <= r; ++i) if (a[i] == x) a[i] = y;
                G[ll].rebuild(x, y);
                for (int i = ll + 1; i <= len; ++i) {
                    b[i][idv[x]] = b[i - 1][idv[x]] + cx[i];
                    b[i][idv[y]] = b[i - 1][idv[y]] + cy[i];
                    c[i][x] = c[i - 1][x] + cntx[i];
                    c[i][y] = c[i - 1][y] + cnty[i];
                }
                continue;
            }
            if (c[ll][x] != c[ll - 1][x]) {//优化边角重构 
                for (int i = ls[ll]; i <= rs[ll]; ++i) a[i] = G[ll].reduct(a[i]);
                for (int i = l; id[i] == ll; ++i) if (a[i] == x) a[i] = y;
                G[ll].rebuild(x, y);
            }
            for (int i = ll + 1; i <= len; i++) {
                if (i > rr || !cntx[i]) {//忽略不存在 x 的块 
                    b[i][idv[x]] = b[i - 1][idv[x]] + cx[i];
                    b[i][idv[y]] = b[i - 1][idv[y]] + cy[i];
                    c[i][x] = c[i - 1][x] + cntx[i];
                    c[i][y] = c[i - 1][y] + cnty[i];
                    continue;
                }
                if (i == rr) {
                    for (int j = ls[rr]; j <= rs[rr]; ++j) a[j] = G[rr].reduct(a[j]);
                    for (int j = r; id[j] == rr; --j) if (a[j] == x) a[j] = y;
                    G[rr].rebuild(x, y);
                    continue;
                }
                if (cnty[i]) {
                    for (int j = ls[i]; j <= rs[i]; ++j) a[j] = G[i].reduct(a[j]);
                    for (int j = ls[i]; j <= rs[i]; ++j) if (a[j] == x) a[j] = y;
                    G[i].rebuild(x, y);
                } else {
                    G[i].mdy(x, y);
                    if (idv[x] != idv[y]) b[i][idv[x]] = b[i - 1][idv[x]] + cx[i] - cntx[i], b[i][idv[y]] = b[i - 1][idv[y]] + cy[i] + cntx[i];
                    else b[i][idv[x]] = b[i - 1][idv[x]] + cx[i];
                    c[i][x] = c[i - 1][x];
                    c[i][y] = c[i - 1][y] + cntx[i];
                }
            }
        } else {
            int k = read(), ll = id[l], rr = id[r];
            if (ll == rr) {
                for (int i = l; i <= r; ++i) {
                    int v = G[ll].reduct(a[i]);
                    if (!tx[idv[v]]) stk1[++top1] = idv[v];
                    tx[idv[v]]++;
                    if (!ty[v]) stk2[++top2] = v;
                    ty[v]++;
                }
                write(qry(k, 1, 0)), puts("");
                while (top1) tx[stk1[top1--]] = 0;
                while (top2) ty[stk2[top2--]] = 0; 
                continue;
            }
            for (int i = l; id[i] == ll; ++i) {
                int v = G[ll].reduct(a[i]);
                if (!tx[idv[v]]) stk1[++top1] = idv[v];
                tx[idv[v]]++;
                if (!ty[v]) stk2[++top2] = v;
                ty[v]++;
            }
            for (int i = r; id[i] == rr; --i) {
                int v = G[rr].reduct(a[i]);
                if (!tx[idv[v]]) stk1[++top1] = idv[v];
                tx[idv[v]]++;
                if (!ty[v]) stk2[++top2] = v;
                ty[v]++;
            }
            write(qry(k, ll + 1, rr - 1)), puts("");
            while (top1) tx[stk1[top1--]] = 0;
            while (top2) ty[stk2[top2--]] = 0;//用栈优化清空的过程 
        }
    }
}