莫队二次离线学习笔记

本文最后更新于:March 18, 2022 pm

例题

$\texttt{Luogu P4887 [模板]莫队二次离线}$

设莫队指针移动时间复杂度为 $O(k)$ ,则普通莫队的时间复杂度为 $O(m\sqrt n k)$ ,使用莫队二次离线可以变成 $O(m\sqrt n + nk)$ 。

本题直接莫队做是 $O(m\sqrt n\binom{14}{k})$ 的,无法通过。

记 $a_x$ 对区间 $[l,r]$ 的贡献为 $f(x,[l,r])$,差分后贡献记为 $f(x,r)-f(x,l-1)$,即 $f(x,r)$ 表示 $x$ 对前缀 $[1,r]$ 的贡献。考虑莫队指针移动过程中答案的变化:

  • $[l,r]\longrightarrow [l,qr]:$

    • $qr > r:$ 答案增加 $\sum\limits_{i=r+1}^{qr}f(i,i-1)-f(i,l-1)$

    • $qr < r:$ 答案减少 $\sum\limits_{i=qr+1}^r f(i,i-1) - f(i,l-1)$

  • $[l,r]\longrightarrow [ql,r]:$

    • $ql < l:$ 答案增加 $\sum\limits_{i=ql}^{l-1} f(i,r)-f(i,i)$

    • $ql > l:$ 答案减少 $\sum\limits_{i=l}^{ql-1} f(i,r)-f(i,i)$

$f(i,i)$ 和 $f(i,i-1)$ 都是固定的,可以预处理直接计算;

$f(i,l-1)$ 和 $f(i,r)$ 则使用扫描线解决,具体地说:

  • 对于每个前缀 $[1,i]$,开一个 $vector$ 存下所有的询问 $f([l,r],i)$,记 $g(x)$ 表示 $a_{[1,i]}$ 中 $\oplus x$ 的二进制表示下恰好有 $k$ 个 $1$ 的个数,则对于询问 $f([l,r],i)$ 的贡献即为 $\sum\limits_{i=l}^r g(a_i)$。

  • 处理完 $[1,i]$ 然后处理 $[1,i+1]$ 时,枚举 $a_{i+1}$ 取反 $k$ 位更新 $g$ 即可。

询问可以用四元组 $(l,r,id,1/-1)$ 表示询问 $f([l,r],i)$,询问编号 $id$ 以及增减 $1/-1$。

$\texttt{Code:}$

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
inline int read() {
    int x = 0, f = 0; char c = getchar();
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
    return f ? -x : x;
}
const int maxn = 1e5 + 5;
int n, m, k, a[maxn], B;
struct _ques {
    int l, r, id, pos;
    bool operator < (const _ques &a) const {
        return pos ^ a.pos ? l < a.l : pos & 1 ? r > a.r : r < a.r;
    }
} qr[maxn];
long long sum[maxn], ans[maxn];
int f[maxn], g[maxn];
vector<int> trs;
struct st {
    int l, r, id, dt;
    st (int l = 0, int r = 0, int id = 0, int dt = 0) : l(l), r(r), id(id), dt(dt) {}
};
vector<st> vec[maxn];
int main() {
    n = read(); m = read(); k = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    B = sqrt(n);
    for (int i = 1; i <= m; i++) {
        int l = read(), r = read();
        qr[i] = (_ques){l, r, i, l / B};
    }
    for (int S = 0; S < (1 << 14); S++) {
        int cnt = 0;
        for (int i = 0; i < 14; i++) cnt += S >> i & 1;
        if (cnt == k) trs.pb(S);
    }
    for (int i = 1; i <= n; i++) {
        f[i] = g[a[i]];
        for (auto S : trs) g[a[i] ^ S]++;
    }
    memset(g, 0, sizeof(g));
    sort(qr + 1, qr + m + 1);
    int l = qr[1].l, r = qr[1].r;
    for (int i = l + 1; i <= r; i++) sum[1] += f[i];
    if (l < r) vec[l - 1].pb(st(l + 1, r, 1, -1));
    for (int i = 2; i <= m; i++) {
        if (r < qr[i].r) vec[l - 1].pb(st(r + 1, qr[i].r, i, -1));
        if (r > qr[i].r) vec[l - 1].pb(st(qr[i].r + 1, r, i, 1));
        while (r < qr[i].r) sum[i] += f[++r];
        while (r > qr[i].r) sum[i] -= f[r--];
        if (l > qr[i].l) vec[r].pb(st(qr[i].l, l - 1, i, 1));
        if (l < qr[i].l) vec[r].pb(st(l, qr[i].l - 1, i, -1));
        while (l > qr[i].l) sum[i] -= f[--l];
        while (l < qr[i].l) sum[i] += f[l++];
    }
    for (int i = 1; i <= n; i++) {
        for (auto S : trs) g[a[i] ^ S]++;
        for (st qry : vec[i]) {
            for (int j = qry.l; j <= qry.r; j++) {
                sum[qry.id] += qry.dt * g[a[j]];
                if (j <= i && k == 0) sum[qry.id] -= qry.dt;
            }
        }
    }
    for (int i = 1; i <= m; i++) {
        sum[i] += sum[i - 1];
        ans[qr[i].id] = sum[i];
    }
    for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]);
    return 0;
}

习题


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!