「Ynoi2018」五彩斑斓的世界

这个好像叫突刺贯穿第二分块吧。

神奇的分块题,先考虑全局查询怎么做,首先发现当做了 \(1\) 操作后会有很多原本不同的值变得相同且以后也一直会相同,并且此题值域 \([0, 10^5 + 1]\) ,相当于把原本两个值「并」在了一起,由此联想到并查集维护,刚好还能维护某个值的数量。

思考怎么保证时间复杂度,以下先忽略并查集时间,我们定义势能为当前所有值中最大值 \(top\) ,除去 \(x \geq top\) ,若 \(1\) 操作的 \(x\) 满足 \(2x > top\) ,则可以暴力枚举 \((x, top]\) 的值 \(v\) 并将 \(v\) 并到 \(v - x\) ,即花费 \(top - x\) 的时间减小了 \(top - x\) 的势能,明显时间复杂度均摊 \(O(V)\)

对于 \(2x \leq top\) ,我们不能像上面那样做,不然时间不对,但思路可以借鉴, \(x\) 小了,我们不能从 \(> x\) 的值入手操作,但可以从 \(< x\) 的值入手啊,转换下操作,将 \(> x\) 的数 \(-x\) 等价于将 \(\leq x\) 的数 \(+x\) ,然后全局 \(-x\) ,所以可以暴力枚举 \((0, x]\) (为什么不包含 \(0\) 等会儿讲)的值 \(v\) 并将 \(v\) 并到 \(v + x\) ,然后打全局减 \(tag\) ,即花费 \(x\) 的时间减小 \(x\) 的势能。

现在来说关于 \(0\) 的问题,首先 \(0\) 只跟初始序列有关,可以单独拎出来处理,其次,由于 \(x\) 会并到 \(2x\) 上去,而 \(0\) 又会并到 \(x\) 上,那 \(0\) 就会直接并到 \(2x\) 上,就出锅了。

现在全局做法讲完了,放到区间操作上就比较模板化,首先线段树这些不支持开值域上并查集,就只能考虑分块,每个块开一个并查集,整块按上述操作做,散块重构,查询同理,但此题卡了空间,块与块之间查询又相对独立,所以要把块离线下来做,对于单个块做一遍 \(Q\) 个查询,这样就不需要开 \(\sqrt{N}\) 个并查集。

最后来讲并查集时间,此题不能写启发式合并,但可以写路径压缩,看似要 \(O(\log N)\) ,实际仔细分析一下,我们对于 \((tag, top]\) 内的所有数,它们都是作为其连通块内的代表元存在的,所以在进行「并」时实际是 \(O(1)\) 的,对于散块重构时,花费 \(O(S)\) (\(S\) 为连通块大小) 的时间重构 \(S\) 个元素,也是 \(O(1)\) 的,所以此题中并查集严格 \(O(1)\)

综上,分块 + 并查集,时间复杂度 \(O(V \sqrt{N} + Q \sqrt{N})\) ,空间复杂度 \(O(V + N)\)

实现参考的这位,目前跑到了最优解\(4\)

/*
address:https://www.luogu.com.cn/problem/P4117
AC 2026/8/1 11:24
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
const int V = 1e5 + 5;
int n, q;
int fa[V], siz[V];
inline void init(int n) { for (int i = 0;i <= n;++i) fa[i] = i, siz[i] = 0; }
inline int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
inline void merge(int x, int y) {
    x = find(x), y = find(y);
    if (x != y) fa[x] = y;
}
inline void read(int& x) {
    x = 0;
    char c = getchar();
    while (c < '0' || c>'9') c = getchar();
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
}
int a[N], sum0[N];
struct query { int op, l, r, x; }qry[N >> 1];
int ans[N >> 1];
int B, blk;
int L[1005], R[1005];
inline void solve(int id) {
    int top = 0, delta = 0;
    for (int i = L[id];i <= R[id];++i) top = max(top, a[i]);
    init(top);
    for (int i = L[id];i <= R[id];++i) ++siz[a[i]];
    for (int i = 1;i <= q;++i)
        if (qry[i].op == 1)
            if (!qry[i].x || top - delta <= qry[i].x || qry[i].l > R[id] || qry[i].r < L[id]) continue;
            else if (qry[i].l > L[id] || qry[i].r < R[id]) {
                for (int j = L[id];j <= R[id];++j) a[j] = find(a[j]);
                for (int j = max(qry[i].l, L[id]);j <= min(qry[i].r, R[id]);++j)
                    if (a[j] - delta > qry[i].x) --siz[a[j]], a[j] -= qry[i].x, ++siz[a[j]];
                while (!siz[top]) --top;
            }
            else
                if (top - delta >= qry[i].x << 1) {
                    for (int j = delta + 1;j <= qry[i].x + delta;++j) {
                        siz[j + qry[i].x] += siz[j], siz[j] = 0;
                        merge(j, j + qry[i].x);
                    }
                    delta += qry[i].x;
                }
                else {
                    for (int j = qry[i].x + delta + 1;j <= top;++j) {
                        siz[j - qry[i].x] += siz[j], siz[j] = 0;
                        merge(j, j - qry[i].x);
                    }
                    while (!siz[top]) --top;
                }
        else
            if (!qry[i].x || qry[i].x > top - delta || qry[i].l > R[id] || qry[i].r < L[id]) continue;
            else if (qry[i].l > L[id] || qry[i].r < R[id]) {
                for (int j = L[id];j <= R[id];++j) a[j] = find(a[j]);
                for (int j = max(L[id], qry[i].l);j <= min(R[id], qry[i].r);++j)
                    ans[i] += a[j] - delta == qry[i].x;
            }
            else ans[i] += siz[qry[i].x + delta];
}
int main() {
    read(n), read(q);
    for (int i = 1;i <= n;++i) read(a[i]), sum0[i] = sum0[i - 1] + !a[i];
    for (int i = 1;i <= q;++i) read(qry[i].op), read(qry[i].l), read(qry[i].r), read(qry[i].x);
    B = min(n, int(sqrt(n << 1)));
    blk = (n + B - 1) / B;
    for (int i = 1;i <= blk;++i) L[i] = (i - 1) * B + 1, R[i] = min(i * B, n);
    for (int i = 1;i <= blk;++i) solve(i);
    for (int i = 1;i <= q;++i)
        if (qry[i].op == 2)
            if (qry[i].x) printf("%d\n", ans[i]);
            else printf("%d\n", sum0[qry[i].r] - sum0[qry[i].l - 1]);
    return 0;
}
posted @ 2026-08-02 10:43  keysky  阅读(3)  评论(0)    收藏  举报