分块和普通莫队入门

分块

分块是一种数据结构,其核心就是暴力。和线段树和树状数组一样,它可以支持区间修改,单点修改,区间查询,单点查询。
总体来讲,可以总结为大段维护,小段暴力。
下面具体讲一下它的实施。
首先,我们了一个数组,假设这个数组的大小是 \(n\),那么我们就把它分成 \(\lceil \sqrt{n} \rceil\) 段,其中 \(\lfloor \sqrt{n} \rfloor\) 段的大小都是 \(\sqrt{n}\),剩下最后一个段的大小就是 \(n - \lfloor \sqrt{n} \rfloor \times \sqrt{n}\)
就像下面这张图一样。(画的太丑乐)

image

分块基本上都可以做到线段树能做的,所以就拿线段树的板子来写一下。

P3372 【模板】线段树 1
对于每一个段,我们可以单独维护两个数组,一个表示这个段里面的和,另一个表示这个块的延迟标记。
我们可以把 \([l, r]\) 这个区间看成这样子。
image

  • 查询:
    那么橙色部分就可以直接维护,让我们的答案直接加上这段的和以及延迟标记乘上块长即可。
    而对于我用绿色圈出来的部分就没有办法了,只能暴力算了。
  • 修改
    一样的,对于橙色部分,我们直接让这一段的延迟标记直接加。
    而绿色部分也暴力维护即可。

然后为了方便写,可以维护一些东西,比如这个块是哪一个区间,大小是多少,所有下标所归属的块的标号等等。

时间复杂度为 \(O(n\sqrt{n})\)

int n, m;
i64 a[N], sum[N], add[N], z;
int l[N], r[N], belong[N], siz[N];
int op, x, y;
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr); std::cout.tie(nullptr);

    std::cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
    }
    int k = std::sqrt(n), kn = 1;
    for (int i = 1; ; i += k, kn++) {
        if (i + k - 1 >= n) {
            l[kn] = i;
            r[kn] = n;
            break;
        } else {
            l[kn] = i;
            r[kn] = i + k - 1;
        }
    }
    for (int i = 1; i <= kn; i++) {
        for (int j = l[i]; j <= r[i]; j++) {
            sum[i] += a[j];
            belong[j] = i;
        }
        siz[i] = r[i] - l[i] + 1;
    }

    while (m--) {
        std::cin >> op >> x >> y;

        if (op == 1) {
            std::cin >> z;

            if (belong[x] == belong[y]) {
                for (int i = x; i <= y; i++) {
                    a[i] += z;
                    sum[belong[x]] += z;
                }
            } else {
                for (int i = x; i <= r[belong[x]]; i++) {
                    a[i] += z;
                    sum[belong[x]] += z;
                }
                for (int i = belong[x] + 1; i <= belong[y] - 1; i++) {
                    add[i] += z;
                }
                for (int i = l[belong[y]]; i <= y; i++) {
                    a[i] += z;
                    sum[belong[y]] += z;
                }
            }
        } else {    
            i64 ans = 0;

            if (belong[x] == belong[y]) {
                for (int i = x; i <= y; i++) {
                    ans += a[i] + add[belong[x]];
                }
            } else {
                for (int i = x; i <= r[belong[x]]; i++) {
                    ans += a[i] + add[belong[x]];
                }
                for (int i = belong[x] + 1; i <= belong[y] - 1; i++) {
                    ans += sum[i] + add[i] * siz[i];
                }
                for (int i = l[belong[y]]; i <= y; i++) {
                    ans += a[i] + add[belong[y]];
                }
            }
            std::cout << ans << "\n";
        }
    }

    return 0;
}

莫队

莫队就是一个离线处理多个询问的算法,因为是离线的,所以普通的莫队不支持修改。然后莫队算法最精髓的东西就是它对询问进行分块。

首先把询问给读入进来,按照字典序排一下序,然后根据这个询问的区间一步一步的拓展区域,这就是莫队的最精髓的东西。然后如果这个拓展的过程可以 \(O(1)\),那么时间复杂度就是 \(O(n\sqrt{n})\),这个东西 OI-Wiki 上有证明,但是我咕咕咕。

然后就是一个拓展区域的问题,应该怎么拓展就是一个问题。

image

OI-Wiki 写的很好,所以我就不写了,这个背一个形式应该就可以吧,我觉得背 l--, r++, l++, r-- 就很好。

P1494 [国家集训队] 小 Z 的袜子

这个题还要用一点排列组合:

但是先咕咕咕者。

好,莫队咕完了。

posted @ 2023-06-03 19:16  落花月朦胧  阅读(42)  评论(0编辑  收藏  举报