莫队二次离线

莫队二次离线

前置芝士

莫队

算法介绍

第一步优化

对于普通的莫队,我们求没有修改的若干个询问,将其离线,对询问分块,经过巧妙的排序,然后对于一个区间 \([l,r]\) 通过计算得到 \([l, r+1], [l, r - 1], [l - 1, r], [l + 1, r]\) 的答案。

设计算单点造成的答案差值的时间复杂度为 \(O(k)\)

那么总的时间复杂度是 \(O(nk\sqrt n)\)

对于某些毒瘤题 由于 \(k\) 太大,这是远远不够的。

那么怎么办呢?

反正都离线了,就乱搞呗

考虑到某些问题单点差值可以表示为一段区间的贡献减去另一端区间的贡献时,

我们可以将其转化为前缀和的形式,进行预处理。

那么此时的差值计算是 \(O(1)\) 的,

总的时间复杂度也就降到了 \(O(nk + n \sqrt n)\)

再次优化

十分的优秀了!可惜还不够。

我们会发现空间是 \(O(n \sqrt n)\) 的,而且常数较大。

那么如何去进一步优化呢?

举个栗子:

你现在的区间是 \([10, 20]\) 你要拓展到 \([10, 25]\)

那么就会被拆为

询问 \([1, 21]\) 对应的 \(r = 21\) 时的贡献

询问 \([1, 22]\) 对应的 \(r = 22\) 时的贡献

\(\cdots\)

询问 \([1, 25]\) 对应的 \(r = 25\) 时的贡献

以上的全都是权值 +1

然后是

询问 \([1, 9]\) 对应的 \(r = 21\) 时的贡献

询问 \([1, 9]\) 对应的 \(r = 22\) 时的贡献

\(\cdots\)

询问 \([1, 9]\) 对应的 \(r = 25\) 时的贡献

以上的全都是权值 -1

那么我们就分为两个部分 +1-1

下面面对 LuoguP4887 具体举例

Case1: 求+1部分的和

那么我们可以跑一边扫描线,对于每一个位置 \(i\) 求出 \([1,i-1]\) 这个前缀有多少个数字和 \(a_{i}\) 异或起来有 \(k\) 个1。

具体点来讲我们实现一个 \(O(k)\) 插入 \(O(1)\) 询问的数组 \(sum\)

假设我们跑扫描线的时候跑到了第 \(i\) 个点那么 \(sum_j\) 就表示 \([1,i]\) 这个区间中有多少个数字和 \(a_{j}\) 异或起来有 \(k\) 个1。

那么我们插入一个 \(a\) 值的时候就直接大力的枚举所有有 \(k\) 个1的数字 \(x\) 然后令 \(sum_{a \oplus x}\) 加一。

接下来我们维护一个变量 \(K\) 表示“对与 \([1, i]\) 中的每一个位置 \(i\) ,满足 \([1, i - 1]\) 这个前缀 \(a_{i}\) 异或起来有k个1的数字的数目的和”。

那么对于上面那个栗子,我们只需要用 \(r = 23\)\(K\) 减去 \(r = 20\)\(K\) 即可。

Case2: 求-1部分的和

我们发现这一部分是对于一个固定前缀询问了一个区间里有多少个数异或起来刚好有 \(k\) 个1。

那么我们可以在前缀右端点开一个 vector 然后存入后面询问的区间,注意这边还要存入一个符号和询问的编号。

然后我们就可以在跑扫描线的时候挨个暴力询问。

我们就如此神奇的把空间压缩成了 \(O(m)\)

例题实现

#include <bits/stdc++.h>
using ll = int64_t;
static constexpr int N = 1e5 + 5;
int n, m, k, a[N], b[N], bel[N], sum[N];
ll ans[N];
class Query {
    public: int l, r, id;
    public: ll ans;
    public: bool operator < (const Query &a) {return bel[l] == bel[a.l] ? r < a.r : l < a.l;}
} q[N];
std::vector<std::tuple<int, int, int>> v[N];
auto main() -> decltype(0) {
    scanf("%d%d%d", &n, &m, &k);

    if (k > 14) {for (int i = 1; i <= m; ++i) puts("0"); return 0;}

    for (int i = 1; i <= n; ++i) scanf("%d", a + i);
    for (int i = 1; i <= m; ++i) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;

    std::vector<int> bucket;
    for (int i = 0; i < (1 << 14); ++i)
        if (__builtin_popcount(i) == k)
            bucket.emplace_back(i);

    int size = std::sqrt(n);
    for (int i = 1; i <= n; ++i) bel[i] = (i - 1) / size + 1;

    std::sort(q + 1, q + m + 1);

    for (int i = 1; i <= n; ++i) {
        for (int x : bucket) ++ b[a[i] ^ x];
        sum[i] = b[a[i + 1]];
    }
    std::memset(b, 0, sizeof b);

    for (int i = 1, l = 1, r = 0; i <= m; ++i) {
        int ql = q[i].l, qr = q[i].r;
        if (l < ql) v[r].emplace_back(l, ql - 1, -i);
        if (l > ql) v[r].emplace_back(ql, l - 1, i);
        while (l < ql) q[i].ans += sum[l - 1], ++ l;
        while (l > ql) q[i].ans -= sum[l - 2], -- l;
        if (r < qr) v[l - 1].emplace_back(r + 1, qr, -i);
        if (r > qr) v[l - 1].emplace_back(qr + 1, r, i);
        while (r < qr) q[i].ans += sum[r], ++ r;
        while (r > qr) q[i].ans -= sum[r - 1], -- r;
    }

    for (int i = 1, l, r, id; i <= n; ++i) {
        for (int j : bucket) ++ b[a[i] ^ j];
        for (auto x : v[i]) {
            std::tie(l, r, id) = x;
            for (int j = l, tmp = 0; j <= r; ++j) {
                tmp = b[a[j]];
                if (j <= i && !k) -- tmp;
                q[std::abs(id)].ans += tmp * (id < 0 ? -1 : 1);
            }
        }
    }

    for (int i = 1; i <= m; ++i) q[i].ans += q[i - 1].ans;
    for (int i = 1; i <= m; ++i) ans[q[i].id] = q[i].ans;
    for (int i = 1; i <= m; ++i) printf("%lld\n", ans[i]);
    return 0;
}
posted @ 2022-09-05 09:51  xxcxu  阅读(42)  评论(0编辑  收藏  举报