莫队

莫队

对于某些序列上的区间询问问题,如果在得知 \([l, r]\) 的答案的情况下,可以 \(O(1)\) 推算出 \([l - 1, r], [l + 1, r], [l, r - 1], [l, r + 1]\) 的答案,那么就可以在 \(O(n \sqrt{m})\) 的时间求出所有询问的答案。

普通莫队

将所有的询问离线后以左端点所在的块为第一关键字,右端点为第二关键字进行排序。按排序后的顺序处理每一个询问,暴力从上一个区间的答案推出当前区间的答案。

注意移动指针时应先扩大区间,后缩小区间。

\(n, m\) 同阶,时间复杂度 \(O(n \sqrt{n})\)

for (int i = 1, l = 1, r = 0; i <= m; ++i) {
    while (l > qry[i].l)
        add(--l);

    while (r < qry[i].r)
        add(++r);
    
    while (l < qry[i].l)
        del(l++);

    while (r > qry[i].r)
        del(r--);

    ans[qry[i].id] = result;
}

一些优化:

  • 适当调整块长。
  • 奇偶性排序:在选择奇数块时,按照右端点从小到大排序,在偶数块时从大到小排序,常数优化为原来的一半。

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

给定 \(a_{1 \sim n}\)\(m\) 次询问区间内任选两个数相同的概率。

\(n, m \le 5 \times 10^4\)

将题意用式子可以表示为:

\[\frac{\sum \frac{cnt_k (cnt_k - 1)}{2}}{\frac{len (len - 1)}{2}} = \frac{\sum cnt_k^2 - \sum cnt_k}{len (len - 1)} = \dfrac{\sum cnt_k^2 - len}{len (len - 1)} \]

问题就转化为求区间数字出现次数平方和。

考虑答案的增量:

\[(a+1)^2=a^2+2a+1 \]

于是就可以很轻松地设计 add 函数和 del 函数了。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 5e4 + 7;

struct Query {
    int l, r, bid, id;

    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : bid < rhs.bid;
    }
} qry[N];

pair<ll, ll> ans[N];

int a[N], cnt[N];

int n, m;

signed main() {
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);

    int block = sqrt(n);

    for (int i = 1; i <= m; ++i)
        scanf("%d%d", &qry[i].l, &qry[i].r), qry[i].bid = qry[i].l / block, qry[i].id = i;
    
    sort(qry + 1, qry + m + 1);
    ll result = 0;

    auto update = [&](int x, int k) {
        result -= 1ll * cnt[x] * cnt[x];
        cnt[x] += k;
        result += 1ll * cnt[x] * cnt[x];
    };

    for (int i = 1, l = 1, r = 0; i <= m; ++i) {
        if (qry[i].l == qry[i].r) {
            ans[qry[i].id] = make_pair(0, 1);
            continue;
        }

        while (l > qry[i].l)
            update(a[--l], 1);

        while (r < qry[i].r)
            update(a[++r], 1);

        while (l < qry[i].l)
            update(a[l++], -1);

        while (r > qry[i].r)
            update(a[r--], -1);

        ans[qry[i].id] = make_pair(result - (r - l + 1), 1ll * (r - l + 1) * (r - l));
    }

    for (int i = 1; i <= m; ++i) {
        ll g = __gcd(ans[i].first, ans[i].second);
        printf("%lld/%lld\n", ans[i].first / g, ans[i].second / g);
    }

    return 0;
}

CF617E XOR and Favorite Number

给定 \(k\)\(m\) 次询问区间内有多少子段异或和为 \(k\)

\(n, m \le 10^5\)\(k, a_i \le 10^6\)

可以先预处理出前缀异或值,由前缀和性质可知,问题转化为有多少对 \(i,j\) 满足 \(s_i \oplus s_j = k\) 。注意到 \(x \oplus y = k\) 等价于 \(x \oplus k = y\) 。于是问题转化为区间数字出现次数。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7, V = 2e6 + 7;

struct Query {
    int l, r, id, bid;

    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
    }
} qry[N];

ll ans[N];
int a[N], cnt[V];

ll result;
int n, m, k, block;

inline void add(int x) {
    result += cnt[a[x] ^ k], ++cnt[a[x]];
}

inline void del(int x) {
    --cnt[a[x]], result -= cnt[a[x] ^ k];
}

signed main() {
    scanf("%d%d%d", &n, &m, &k), block = n / sqrt(m) + 1;

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i), a[i] ^= a[i - 1];

    for (int i = 1; i <= m; ++i) {
        scanf("%d%d", &qry[i].l, &qry[i].r);
        --qry[i].l, qry[i].id = i, qry[i].bid = qry[i].l / block;
    }

    sort(qry + 1, qry + 1 + m);

    for (int i = 1, l = 1, r = 0; i <= m; ++i) {
        while (l > qry[i].l)
            add(--l);

        while (r < qry[i].r)
            add(++r);

        while (l < qry[i].l)
            del(l++);

        while (r > qry[i].r)
            del(r--);

        ans[qry[i].id] = result;
    }

    for (int i = 1; i <= m; ++i)
        printf("%lld\n", ans[i]);

    return 0;
}

带修莫队

强行加上一维时间维,表示该操作的时间。在查询时修改,改多了就还原回来,改少了就改过去。

待修莫队的排序通常是这么排:

struct Query {
    int l, r, t, id;

    inline bool operator < (const Query &rhs) const {
        return bel[l] == bel[rhs.l] ? (bel[r] == bel[rhs.r] ? t < rhs.t : r < rhs.r) : l < rhs.l;
    }
} qry[N];

\(n\) 为序列长度,\(m\) 次询问, \(t\) 次修改,则最佳块长为 \(\sqrt[3]{\frac{n^2 t}{m}}\) ,实际操作取块长为 \(n^{\frac{2}{3}}\) 即可。当 \(n, m, t\) 同数量级时,时间复杂度为 \(O(n^{\frac{5}{3}})\)

P1903 [国家集训队] 数颜色 / 维护队列

给出序列 \(a_{1 \sim n}\)\(m\) 次操作,操作有:

  • Q l r :求 \(a_{l \sim r}\) 中颜色种数。
  • R p c :将 \(a_p\) 改为颜色 \(c\)

\(n, m \le 133333\)

注意一下修改操作 \((x, k)\) 的影响,当且仅当 \(x \in [l, r]\) 才要统计。

考虑如何撤销操作,每次直接交换 \(a_x\)\(k\) ,这样下次修改就相当于撤销了。

#include <bits/stdc++.h>
using namespace std;
const int N = 1.5e5 + 7, V = 1e6 + 7;

int bel[N];

struct Query {
    int l, r, t, id;

    inline bool operator < (const Query &rhs) const {
        return bel[l] == bel[rhs.l] ? (bel[r] == bel[rhs.r] ? t < rhs.t : r < rhs.r) : l < rhs.l;
    }
} qry[N];

pair<int, int> upd[N];

int a[N], cnt[V], ans[N];

int n, m, cntu, cntq;

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    int block = pow(n, 0.667);

    for (int i = 1; i <= n; ++i)
        cin >> a[i], bel[i] = i / block;

    for (int i = 1; i <= m; ++i) {
        char op;
        int x, y;
        cin >> op >> x >> y;

        if (op == 'R')
            upd[++cntu] = make_pair(x, y);
        else
            ++cntq, qry[cntq] = (Query) {x, y, cntu, cntq};
    }

    sort(qry + 1, qry + cntq + 1);

    for (int i = 1, l = 1, r = 0, t = 0, result = 0; i <= cntq; ++i) {
        auto update = [&](int x, int k) {
            if (!cnt[x])
                ++result;

            cnt[x] += k;

            if (!cnt[x])
                --result;
        };

        while (l > qry[i].l)
            update(a[--l], 1);

        while (r < qry[i].r)
            update(a[++r], 1);

        while (l < qry[i].l)
            update(a[l++], -1);

        while (r > qry[i].r)
            update(a[r--], -1);

        auto modify = [&](int x) {
            if (l <= upd[x].first && upd[x].first <= r)
                update(a[upd[x].first], -1), update(upd[x].second, 1);

            swap(a[upd[x].first], upd[x].second);
        };

        while (t < qry[i].t)
            modify(++t);

        while (t > qry[i].t)
            modify(t--);

        ans[qry[i].id] = result;
    }

    for (int i = 1; i <= cntq; ++i)
        printf("%d\n", ans[i]);

    return 0;
}

树上莫队

通常用于处理树上路径信息统计问题,考虑将树的括号序跑下来,在括号序上跑莫队。

设点 \(i\) 入栈时时间戳为 \(st_i\) ,出栈时时间戳为 \(ed_i\) ,查询 \(u \to v\) 路径上的信息时分类讨论(钦定 \(dfn_u < dfn_v\) ):

  • \(u\)\(v\) 的祖先:查询 \([st_u, st_v]\) 即可。
  • \(u\) 不是 \(v\) 的祖先:查询 \([ed_u, st_v]\)\(LCA(u, v)\) 即可。

注意查询时应忽略出现两次的点,括号序要开两倍内存。

时间复杂度和普通莫队是一样的。

SP10707 COT2 - Count on a tree II

给定一棵 \(n\) 个点的树,\(m\) 次查询 \(u \to v\) 路径上节点颜色种数。

\(n \le 4 \times 10^4, m \le 10^5\)

模板,下给出参考代码。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;

struct Graph {
    vector<int> e[N];
    
    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

struct Query {
    int l, r, lca, id, bid;

    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
    }
} qry[N];

int a[N], fa[N], dep[N], siz[N], son[N], top[N], st[N], ed[N], seq[N], cnt[N], ans[N];
bool tag[N];

int n, m, block, dfstime, result;

void dfs1(int u, int f) {
    fa[u] = f, dep[u] = dep[f] + 1, siz[u] = 1;
    int mxsiz = -1;

    for (int v : G.e[u]) {
        if (v == f)
            continue;

        dfs1(v, u), siz[u] += siz[v];

        if (siz[v] > mxsiz)
            son[u] = v, mxsiz = siz[v];
    }
}

void dfs2(int u, int topf) {
    top[u] = topf, seq[st[u] = ++dfstime] = u;

    if (son[u])
        dfs2(son[u], topf);

    for (int v : G.e[u])
        if (v != fa[u] && v != son[u])
            dfs2(v, v);

    seq[ed[u] = ++dfstime] = u;
}

inline int LCA(int x, int y) {
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]])
            swap(x, y);

        x = fa[top[x]];
    }

    return dep[x] < dep[y] ? x : y;
}

inline void add(int x) {
    if (!cnt[a[x]])
        ++result;

    ++cnt[a[x]];
}

inline void del(int x) {
    --cnt[a[x]];

    if (!cnt[a[x]])
        --result;
}

inline void update(int x) {
    if (tag[x])
        del(x), tag[x] = false;
    else
        add(x), tag[x] = true;
}

signed main() {
    scanf("%d%d", &n, &m);
    vector<int> vec;

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i), vec.emplace_back(a[i]);

    sort(vec.begin(), vec.end()), vec.erase(unique(vec.begin(), vec.end()), vec.end());

    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin();

    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G.insert(u, v), G.insert(v, u);
    }

    dfs1(1, 0), dfs2(1, 1);
    block = n * 2 / sqrt(m * 0.667);

    for (int i = 1; i <= m; ++i) {
        int x, y;
        scanf("%d%d", &x, &y);

        if (st[x] > st[y])
            swap(x, y);

        qry[i].id = i, qry[i].lca = LCA(x, y);

        if (qry[i].lca == x)
            qry[i].l = st[x], qry[i].r = st[y], qry[i].lca = 0;
        else
            qry[i].l = ed[x], qry[i].r = st[y];

        qry[i].bid = qry[i].l / block;
    }

    sort(qry + 1, qry + 1 + m);

    for (int i = 1, l = 1, r = 0; i <= m; ++i) {
        while (l > qry[i].l)
            update(seq[--l]);

        while (r < qry[i].r)
            update(seq[++r]);

        while (l < qry[i].l)
            update(seq[l++]);

        while (r > qry[i].r)
            update(seq[r--]);

        if (qry[i].lca)
            update(qry[i].lca);

        ans[qry[i].id] = result;

        if (qry[i].lca)
            update(qry[i].lca);
    }

    for (int i = 1; i <= m; ++i)
        printf("%d\n", ans[i]);

    return 0;
}

P4074 [WC2013] 糖果公园

给定一棵树,\(q\) 次询问 \(u \to v\) 路径上 \(\sum a_{c_i} \sum_{j = 1}^{cnt_{c_i}} w_i\) 的值,其中 \(a_{c_i}\) 表示颜色 \(c_i\) 的价值,\(cnt_{c_i}\) 表示 \(c_i\) 出现次数,\(w_i\) 表示出现 \(i\) 次的价值。

\(n, m, q \le 10^5\)

树上带修莫队模板,下给出参考代码。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7, LOGN = 19;

struct Graph {
    vector<int> e[N];
    
    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

int bel[N];

struct Query {
    int l, r, lca, t, id;

    inline bool operator < (const Query &rhs) const {
        return bel[l] == bel[rhs.l] ? (bel[r] == bel[rhs.r] ? t < rhs.t : r < rhs.r) : l < rhs.l;
    }
} qry[N];

pair<int, int> upd[N];

ll ans[N];
int fa[N][LOGN], dep[N], st[N], ed[N], seq[N], cnt[N], v[N], w[N], c[N];
bool tag[N];

int n, m, q, dfstime, cntu, cntq;

void dfs(int u, int f) {
    fa[u][0] = f, dep[u] = dep[f] + 1, seq[st[u] = ++dfstime] = u;

    for (int i = 1; i < LOGN; ++i)
        fa[u][i] = fa[fa[u][i - 1]][i - 1];

    for (int v : G.e[u])
        if (v != f)
            dfs(v, u);

    seq[ed[u] = ++dfstime] = u;
}

inline int LCA(int x, int y) {
    if (dep[x] < dep[y])
        swap(x, y);

    for (int i = 0, h = dep[x] - dep[y]; h; ++i, h >>= 1)
        if (h & 1)
            x = fa[x][i];

    if (x == y)
        return x;

    for (int i = LOGN - 1; ~i; --i)
        if (fa[x][i] != fa[y][i])
            x = fa[x][i], y = fa[y][i];

    return fa[x][0];
}

signed main() {
    scanf("%d%d%d", &n, &m, &q);

    for (int i = 1; i <= m; ++i)
        scanf("%d", v + i);

    for (int i = 1; i <= n; ++i)
        scanf("%d", w + i);

    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G.insert(u, v), G.insert(v, u);
    }

    for (int i = 1; i <= n; ++i)
        scanf("%d", c + i);

    dfs(1, 0);
    int block = pow(dfstime, 0.667);

    for (int i = 1; i <= dfstime; ++i)
        bel[i] = i / block;

    for (int i = 1; i <= q; ++i) {
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);

        if (op) {
            if (st[x] > st[y])
                swap(x, y);

            qry[++cntq].t = cntu, qry[cntq].id = cntq, qry[cntq].lca = LCA(x, y);

            if (qry[cntq].lca == x)
                qry[cntq].l = st[x], qry[cntq].r = st[y], qry[cntq].lca = 0;
            else
                qry[cntq].l = ed[x], qry[cntq].r = st[y];
        } else
            upd[++cntu] = make_pair(x, y);
    }

    sort(qry + 1, qry + 1 + cntq);
    ll result = 0;

    for (int i = 1, l = 1, r = 0, t = 0; i <= cntq; ++i) {
        auto update = [&](int x) {
            if (tag[x])
                result -= 1ll * v[c[x]] * w[cnt[c[x]]--], tag[x] = false;
            else
                result += 1ll * v[c[x]] * w[++cnt[c[x]]], tag[x] = true;
        };

        while (l > qry[i].l)
            update(seq[--l]);

        while (r < qry[i].r)
            update(seq[++r]);

        while (l < qry[i].l)
            update(seq[l++]);

        while (r > qry[i].r)
            update(seq[r--]);

        auto modify = [&](int t) {
            if (tag[upd[t].first])
                update(upd[t].first), swap(c[upd[t].first], upd[t].second), update(upd[t].first);
            else
                swap(c[upd[t].first], upd[t].second);
        };

        while (t < qry[i].t)
            modify(++t);

        while (t > qry[i].t)
            modify(t--);

        if (qry[i].lca)
            update(qry[i].lca);

        ans[qry[i].id] = result;

        if (qry[i].lca)
            update(qry[i].lca);
    }

    for (int i = 1; i <= cntq; ++i)
        printf("%lld\n", ans[i]);

    return 0;
}

回滚莫队

对于一类可离线问题:

  • 有些信息区间伸长时很好维护,区间缩短时却不好维护。
  • 有些信息区间缩短时很好维护,区间伸长时却不好维护。

此时可以考虑回滚莫队。

只增不删回滚莫队

首先将询问排序,需要保证左端点在同一块内的询问的右端点单调不降。

若区间端点在同一块内,直接暴力解决即可。

否则每次处理询问时,令 \(l\) 指针指向块尾,\(r\) 指针指向块尾。先移动 \(r\) 指针,由于右端点不降,于是只要插入即可。保留下来信息,后移动 \(l\) 指针求解,处理完后用前面保留下来的信息恢复即可。

设分块大小为 \(B\) ,则时间复杂度为 \(O(mB + \frac{n^2}{B})\) ,当 \(B= \frac{n}{\sqrt{m}}\) 时复杂度最优为 \(O(n \sqrt{m})\)

只删不增回滚莫队

首先将询问排序,需要保证左端点在同一块内的询问的右端点单调不升。

若区间端点在同一块内,直接暴力解决即可。

否则每次处理询问时,令 \(l\) 指针指向块头,\(r\) 指针指向 \(n\) 。先移动 \(r\) 指针,由于右端点不升,于是只要删除即可。保留下来信息,后移动 \(l\) 指针求解,处理完后用前面保留下来的信息恢复即可。

设分块大小为 \(B\) ,则时间复杂度为 \(O(mB + \frac{n^2}{B})\) ,当 \(B= \frac{n}{\sqrt{m}}\) 时复杂度最优为 \(O(n \sqrt{m})\)

应用

JOISC2014 歴史の研究

\(m\) 次询问区间内 \(\max a_i \times cnt_{a_i}\)

\(n, m \le 10^5\)

模板题,下给出参考代码。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;

int bid[N];

struct Query {
    int l, r, id;
    
    inline bool operator < (const Query &b) const {
        return bid[l] == bid[b.l] ? r < b.r : l < b.l;
    }
} qry[N];

vector<int> vec;

ll ans[N];
int a[N], L[N], R[N], cnt[N], BFcnt[N];

ll result;
int n, m, block, tot;

inline void prework() {
    block = pow(n, 0.666) + 1;

    while (++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
        fill(bid + L[tot], bid + R[tot] + 1, tot);

        if (R[tot] == n)
            break;
    }
}

inline ll BruteForce(int l, int r) {
    ll BFres = 0;
    
    for (int i = l; i <= r; ++i)
        BFres = max(BFres, 1ll * (++BFcnt[a[i]]) * vec[a[i]]);

    for (int i = l; i <= r; ++i)
        --BFcnt[a[i]];
    
    return BFres;
}

signed main() {
    scanf("%d%d", &n, &m), prework();
    
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i), vec.emplace_back(a[i]);
    
    sort(vec.begin(), vec.end()), vec.erase(unique(vec.begin(), vec.end()), vec.end());
    
    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin();
    
    for (int i = 1; i <= m; ++i)
        scanf("%d%d", &qry[i].l, &qry[i].r), qry[i].id = i;
    
    sort(qry + 1, qry + 1 + m);
    
    for (int i = 1, pos = 1; i <= tot; ++i) {
        memset(cnt, 0, sizeof(int) * vec.size());
        result = 0;
        
        for (int r = R[i], l = R[i] + 1; pos <= m && bid[qry[pos].l] == i; ++pos) {
            if (bid[qry[pos].r] == i) {
                ans[qry[pos].id] = BruteForce(qry[pos].l, qry[pos].r);
                continue;
            }
            
            while (r < qry[pos].r)
                ++cnt[a[++r]], result = max(result, 1ll * cnt[a[r]] * vec[a[r]]);
            
            ll nowresult = result;
            
            while (l > qry[pos].l)
                ++cnt[a[--l]], nowresult = max(nowresult, 1ll * cnt[a[l]] * vec[a[l]]);
            
            ans[qry[pos].id] = nowresult;
            
            while (l <= R[i])
                --cnt[a[l++]];
        }
    }
    
    for (int i = 1; i <= m; ++i)
        printf("%lld\n", ans[i]);
    
    return 0;
}

P5906 【模板】回滚莫队&不删除莫队

\(m\) 次询问区间中相同的数的最大出现下标差。

\(n, m \le 2 \times 10^5\)

模板题,下给出参考代码。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7;

int bid[N];

struct Query {
    int l, r, id;
    
    inline bool operator < (const Query &b) const {
        return bid[l] == bid[b.l] ? r < b.r : l < b.l;
    }
} qry[N];

vector<int> vec;

int a[N], L[N], R[N], lpos[N], rpos[N], BFlpos[N], BFrpos[N], ans[N];

int n, m, block, tot;

inline void prework() {
    block = pow(n, 0.666) + 1;

    while (++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
        fill(bid + L[tot], bid + R[tot] + 1, tot);

        if (R[tot] == n)
            break;
    }
}

inline int BruteForce(int l, int r) {
    int result = 0;
    
    for (int i = l; i <= r; ++i) {
        BFlpos[a[i]] = min(BFlpos[a[i]], i), BFrpos[a[i]] = max(BFrpos[a[i]], i);
        result = max(result, BFrpos[a[i]] - BFlpos[a[i]]);
    }

    for (int i = l; i <= r; ++i)
        BFlpos[a[i]] = n + 1, BFrpos[a[i]] = 0;
    
    return result;
}

signed main() {
    scanf("%d", &n), prework();
    
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i), vec.emplace_back(a[i]);
    
    sort(vec.begin(), vec.end()), vec.erase(unique(vec.begin(), vec.end()), vec.end());
    
    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin();

    scanf("%d", &m);
    
    for (int i = 1; i <= m; ++i)
        scanf("%d%d", &qry[i].l, &qry[i].r), qry[i].id = i;
    
    sort(qry + 1, qry + 1 + m);
    fill(BFlpos, BFlpos + vec.size(), n + 1), fill(BFrpos, BFrpos + vec.size(), 0);
    
    for (int i = 1, pos = 1; i <= tot; ++i) {
        fill(lpos, lpos + vec.size(), n + 1), fill(rpos, rpos + vec.size(), 0);
        int result = 0;
        
        for (int r = R[i], l = R[i] + 1; pos <= m && bid[qry[pos].l] == i; ++pos) {
            if (bid[qry[pos].r] == i) {
                ans[qry[pos].id] = BruteForce(qry[pos].l, qry[pos].r);
                continue;
            }
            
            while (r < qry[pos].r) {
                ++r;
                lpos[a[r]] = min(lpos[a[r]], r), rpos[a[r]] = max(rpos[a[r]], r);
                result = max(result, rpos[a[r]] - lpos[a[r]]);
            }
            
            int nowresult = result;
            
            while (l > qry[pos].l) {
                --l;
                BFlpos[a[l]] = min(BFlpos[a[l]], l), BFrpos[a[l]] = max(BFrpos[a[l]], l);
                nowresult = max(nowresult, max(rpos[a[l]], BFrpos[a[l]]) - min(lpos[a[l]], BFlpos[a[l]]));

            }           
            ans[qry[pos].id] = nowresult;

            while (l <= R[i])
                BFlpos[a[l]] = n + 1, BFrpos[a[l]] = 0, ++l;
        }
    }
    
    for (int i = 1; i <= m; ++i)
        printf("%d\n", ans[i]);
    
    return 0;
}

二维莫队

每个状态有四个方向可以扩展,每次移动指针要操作一行或者一列的数,具体实现方式与普通的一维莫队类似。

取块长 \(B = n \times q^{-0.25}\) ,总时间复杂度为 \(O(n^2 \times q^{0.75})\)

P1527 [国家集训队] 矩阵乘法

\(q\) 次询问 \(n \times n\) 矩阵中一个子矩阵的第 \(k\) 小数。

\(n \le 500, q \le 60000\)

先离散化,用莫队维护出矩形中出现的数,再值域分块即可。

这题好像二维莫队过不去,只有 \(60\) 分。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e2 + 7, M = 6e4 + 7;

int bid[N];

struct Query {
    int u, l, d, r, k, id;

    inline bool operator < (const Query &rhs) const {
        if (bid[u] == bid[rhs.u]) {
            if (bid[l] == bid[rhs.l]) {
                if (bid[r] == bid[rhs.r])
                    return bid[r] & 1 ? d < rhs.d : d > rhs.d;
                else
                    return bid[l] & 1 ? r < rhs.r : r > rhs.r;
            } else
                return bid[l] < bid[rhs.l];
        } else
            return bid[u] < bid[rhs.u];
    }
} qry[M];

vector<int> vec;

int a[N][N], cnt[N * N], s[N], ans[M];

int n, m, block, vblock;

inline void add1(int l, int r, int x) {
    for (int i = l; i <= r; ++i)
        ++cnt[a[x][i]], ++s[a[x][i] / vblock];
}

inline void add2(int u, int d, int x) {
    for (int i = u; i <= d; ++i)
        ++cnt[a[i][x]], ++s[a[i][x] / vblock];
}

inline void del1(int l, int r, int x) {
    for (int i = l; i <= r; ++i)
        --cnt[a[x][i]], --s[a[x][i] / vblock];
}

inline void del2(int u, int d, int x) {
    for (int i = u; i <= d; ++i)
        --cnt[a[i][x]], --s[a[i][x] / vblock];
}

inline int query(int k) {
    int cur = 0;

    while (k > s[cur])
        k -= s[cur++];

    cur *= vblock;

    while (k > cnt[cur])
        k -= cnt[cur++];

    return cur;
}

signed main() {
    scanf("%d%d", &n, &m), block = n / pow(m, 0.25) / 3 + 1;

    for (int i = 1; i <= n; ++i)
        bid[i] = (i - 1) / block + 1;

    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            scanf("%d", a[i] + j), vec.emplace_back(a[i][j]);

    sort(vec.begin(), vec.end()), vec.erase(unique(vec.begin(), vec.end()), vec.end());
    vblock = sqrt(vec.size());

    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            a[i][j] = lower_bound(vec.begin(), vec.end(), a[i][j]) - vec.begin();

    for (int i = 1; i <= m; ++i)
        scanf("%d%d%d%d%d", &qry[i].u, &qry[i].l, &qry[i].d, &qry[i].r, &qry[i].k), qry[i].id = i;

    sort(qry + 1, qry + 1 + m);

    for (int i = 1, u = 1, l = 1, d = 0, r = 0; i <= m; ++i) {
        while (u > qry[i].u)
            add1(l, r, --u);

        while (l > qry[i].l)
            add2(u, d, --l);

        while (d < qry[i].d)
            add1(l, r, ++d);

        while (r < qry[i].r)
            add2(u, d, ++r);

        while (u < qry[i].u)
            del1(l, r, u++);

        while (l < qry[i].l)
            del2(u, d, l++);

        while (d > qry[i].d)
            del1(l, r, d--);

        while (r > qry[i].r)
            del2(u, d, r--);

        ans[qry[i].id] = vec[query(qry[i].k)];
    }

    for (int i = 1; i <= m; ++i)
        printf("%d\n", ans[i]);

    return 0;
}

莫队二次离线

适用范围:可以莫队,但移动端点的复杂度并非 \(O(1)\)

考虑把莫队移动的这些端点也都离线下来最后一起算,因为莫队时一次离线,最后处理时又一次离线,故称为莫队二次离线。

假设更新答案的时间复杂度为 \(O(k)\) ,则使用二次离线莫队可以将时间复杂度从 \(O(mk \sqrt{n})\) 降到 \(O(mk + n \sqrt{n})\)

考虑端点移动对答案的影响,以 \([l, r] \rightarrow [l, r + k]\) 为例,设 \(x\) 对区间 \([l, r]\) 的贡献为 \(f(x, [l, r])\) ,则考虑求 \(\forall x \in [r + 1, r + k]\)\(\sum f(x, [l, x - 1])\) 的值。可以差分:

\[f(x, [l, x - 1]) = f(x, [1, x - 1]) - f(x, [1, l - 1]) \]

对于 \(f(x, [1, x - 1])\) 可以直接预处理,对于 \(f(x, [1, l - 1])\) 在每个 \(l - 1\) 的位置挂一个 \(r + 1 \sim r + k\) 的询问即可。因为空间存不下所以需要存区间 \([r + 1, r + k]\) 而不能存 \(k\) 个询问,但是查询时可以直接暴力枚举 \(r + 1 \sim r + k\)

注意最后的结果需要做一次前缀和,因为莫队维护的是答案的变化量,并且最后查询的复杂度必须为 \(O(1)\) 最终复杂度才是正确的。一般来说由于是点对区间的查询,所以可以做到较低复杂度的查询,有时也可以用 \(O(\sqrt{n}) - O(1)\) 的块状数组平衡复杂度。

P4887 【模板】莫队二次离线(第十四分块(前体))

\(m\) 次查询,每次给出 \(l, r, k\) ,求满足 \(l \le i < j \le r, \mathrm{popcount}(a_i \oplus a_j) = k\) 的二元组 \((i,j)\) 的个数。

\(n, m \le 10^5\)\(a_i < 2^{14}\)

预处理 \(\mathrm{popcount} = k\) 的数字集合 \(S\) ,则每次新加入 \(a_i\) 时就暴力遍历 \(S\) 里面的元素做修改,这样查询可以做到 \(O(1)\)

时间复杂度 \(O(m \sqrt{n} + n \binom{14}{k})\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7, V = 1 << 14;

struct Range {
    int l, r, id;

    inline Range(int _l, int _r, int _id) : l(_l), r(_r), id(_id) {}
};

struct Query {
    int l, r, id, bid;
    
    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? r < rhs.r : l < rhs.l;
    }
} qry[N];

vector<Range> ask[N];

ll res[N], ans[N];
int a[N], f[N], g[V];

int n, m, k, block;

signed main() {
    scanf("%d%d%d", &n, &m, &k), block = sqrt(n);
    
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);
    
    vector<int> S;
    
    for (int i = 0; i < V; ++i)
        if (__builtin_popcount(i) == k)
            S.emplace_back(i);
    
    for (int i = 1; i <= n; ++i) {
        f[i] = g[a[i]];

        for (int x : S)
            ++g[x ^ a[i]];
    }
    
    for (int i = 1; i <= m; ++i)
        scanf("%d%d", &qry[i].l, &qry[i].r), qry[i].id = i, qry[i].bid = qry[i].l / block;
    
    sort(qry + 1, qry + m + 1);
    
    for (int i = 1, l = 1, r = 0; i <= m; ++i) {
        if (l > qry[i].l)
            ask[r].emplace_back(qry[i].l, l - 1, i);
        
        while (l > qry[i].l)
            res[i] -= f[--l];

        if (r < qry[i].r)
            ask[l - 1].emplace_back(r + 1, qry[i].r, -i);
        
        while (r < qry[i].r)
            res[i] += f[++r];
        
        if (l < qry[i].l)
            ask[r].emplace_back(l, qry[i].l - 1, -i);
        
        while (l < qry[i].l)
            res[i] += f[l++];
        
        if (r > qry[i].r)
            ask[l - 1].emplace_back(qry[i].r + 1, r, i);
        
        while (r > qry[i].r)
            res[i] -= f[r--];
    }
    
    memset(g, 0, sizeof(g));
    
    for (int i = 1; i <= n; ++i) {
        for (int x : S)
            ++g[x ^ a[i]];
        
        for (Range it : ask[i])
            for (int j = it.l; j <= it.r; ++j) {
                if (it.id > 0)
                    res[it.id] += g[a[j]] - (!k && j <= i);
                else
                    res[-it.id] -= g[a[j]] - (!k && j <= i);
            }
    }
    
    for (int i = 1; i <= m; ++i)
        ans[qry[i].id] = (res[i] += res[i - 1]);
    
    for (int i = 1; i <= m; ++i)
        printf("%lld\n", ans[i]);
    
    return 0;
}

[Ynoi2019 模拟赛] Yuno loves sqrt technology II

给你一个长为 \(n\) 的序列 \(a\)\(m\) 次询问区间的逆序对数。

\(n \le 10^5\)

首先直接莫队是 \(O(n \sqrt{n} \log n)\) 的,然后二次离线就可以做到 \(O(n \sqrt{n} + n \log n)\) 了,注意逆序对下标和值的关系。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;

struct Query {
    int l, r, id, bid;
    
    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? r < rhs.r : l < rhs.l;
    }
} qry[N];

vector<tuple<int, int, int> > askl[N], askr[N];

ll pre[N], suf[N], res[N], ans[N];
int a[N];

int n, m, block;

namespace FK {
int tag[N], sum[N], bel[N];

inline void prework() {
    for (int i = 1; i <= n; ++i)
        bel[i] = i / block + 1, tag[i] = sum[i] = 0;
}

inline void update(int x) {
    for (int i = x; i <= n && bel[i] == bel[x]; ++i)
        ++sum[i];

    for (int i = bel[x] + 1; i <= bel[n]; ++i)
        ++tag[i];
}

inline int query(int x) {
    return tag[bel[x]] + sum[x];
}
} // namespace FK

signed main() {
    scanf("%d%d", &n, &m), block = sqrt(n);
    vector<int> vec;
    
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i), vec.emplace_back(a[i]);
    
    sort(vec.begin(), vec.end()), vec.erase(unique(vec.begin(), vec.end()), vec.end());
    
    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin() + 1;
    
    FK::prework();
    
    for (int i = 1; i <= n; ++i)
        pre[i] = pre[i - 1] + i - 1 - FK::query(a[i]), FK::update(a[i]);
    
    FK::prework();
    
    for (int i = n; i; --i)
        suf[i] = suf[i + 1] + FK::query(a[i] - 1), FK::update(a[i]);
    
    for (int i = 1; i <= m; ++i)
        scanf("%d%d", &qry[i].l, &qry[i].r), qry[i].id = i, qry[i].bid = qry[i].l / block;
    
    sort(qry + 1, qry + 1 + m);
    
    for (int i = 1, l = 1, r = 0; i <= m; ++i) {
        res[i] += (suf[qry[i].l] - suf[l]) + (pre[qry[i].r] - pre[r]);

        if (l > qry[i].l)
            askr[qry[i].r + 1].emplace_back(qry[i].l, l - 1, -i);
        
        if (r < qry[i].r)
            askl[l - 1].emplace_back(r + 1, qry[i].r, -i);
        
        if (l < qry[i].l)
            askr[qry[i].r + 1].emplace_back(l, qry[i].l - 1, i);
        
        if (r > qry[i].r)
            askl[l - 1].emplace_back(qry[i].r + 1, r, i);

        l = qry[i].l, r = qry[i].r;
    }
    
    FK::prework();
    
    for (int i = 1; i <= n; ++i) {
        FK::update(a[i]);
        
        for (auto it : askl[i])
            for (int j = get<0>(it); j <= get<1>(it); ++j) {
                if (get<2>(it) > 0)
                    res[get<2>(it)] += i - FK::query(a[j]);
                else
                    res[-get<2>(it)] -= i - FK::query(a[j]);
            }
    }
    
    FK::prework();
    
    for (int i = n; i; --i) {
        FK::update(a[i]);
        
        for (auto it : askr[i])
            for (int j = get<0>(it); j <= get<1>(it); ++j) {
                if (get<2>(it) > 0)
                    res[get<2>(it)] += FK::query(a[j] - 1);
                else
                    res[-get<2>(it)] -= FK::query(a[j] - 1);
            }
    }
    
    for (int i = 1; i <= m; ++i)
        ans[qry[i].id] = (res[i] += res[i - 1]);
    
    for (int i = 1; i <= m; ++i)
        printf("%lld\n", ans[i]);
    
    return 0;
}

P7448 [Ynoi2007] rdiq / P7601 [THUPC2021] 区间本质不同逆序对

给定 \(a_{1 \sim n}\)\(m\) 询问区间本质不同逆序对数量,其中本质不同定义为逆序对的两个数不同。

\(n \le 10^5\)\(m \le 5 \times 10^5\)

考虑莫队二次离线。由于两侧加入和删除都是等价的,下面讨论 \([l, r]\) 右边加入一个数 \(a_{r + 1}\) 的贡献。

\(lst_x\) 表示上一次 \(a_x\) 出现的位置,\(f(l, r)\) 表示 \([l, r)\)\(> a_r\) 的数的数量,则贡献为 \(f(l, r) - f(l, lst_r)\) 。问题转化为 \(n\) 次单点加、查 \(n \sqrt{m}\) 次二维前缀和,注意这里单点加类似数颜色,即每次加入 \(a_i\) 是需要将上一个 \(a_i\) 删去。

首先将相同的 \(a_r\) 直接加以区分,相同的数越靠左越大,让序列变成一个排列。不难证明这样答案仍正确。将询问转化为 \([l, r]\)\(\le a_r\) 的数的数量,则询问为一个矩形,将其按四种方式分别划分为:

  • \(n^{0.5}\)\(n^{0.75}\times n^{0.75}\) 的块。
    • 一次修改或查询只需要 \(\frac{n}{n^{0.75}} \times \frac{n}{n^{0.75}} = n^{0.5}\) 个块的前缀和。
  • \(n^{0.75}\)\(n^{0.75}\times n^{0.5}\) 的块。
    • 一次修改或查询只需要 \(\frac{n}{n^{0.75}} \times \frac{n^{0.75}}{n^{0.5}} = n^{0.5}\) 个块的前缀和。
  • \(n^{0.75}\)\(n^{0.5}\times n^{0.75}\) 的块。
    • 一次修改或查询只需要 \(\frac{n^{0.75}}{n^{0.5}} \times \frac{n}{n^{0.75}} = n^{0.5}\) 个块的前缀和。
  • \(n\)\(n^{0.5}\times n^{0.5}\) 的块。
    • 一次修改或查询只需要 \(\frac{n^{0.75}}{n^{0.5}} \times \frac{n^{0.75}}{n^{0.5}} = n^{0.5}\) 个块的前缀和。

但是还有若干长或宽 \(< n^{0.5}\) 的部分,此部分无法直接维护前缀和。考虑反向维护,对于每个元素,考虑其对那些询问产生贡献,即覆盖了该元素但没有覆盖该元素所在 \(n^{0.5} \times n^{0.5}\) 块右上角的位置。由于询问都形如 \((r, lst_r)\) ,因此只要暴力枚举两维 \(n^{0.5}\) 个可能的询问,统计贡献即可。

时间复杂度 \(O(n \sqrt{m} + n \sqrt{n})\) ,代码中写的是是查询后缀和。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7, M = 5e5 + 7, B1 = 29, B2 = 429;

struct Query {
    int l, r, id, bid;

    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
    }
} qry[M];

vector<tuple<int, int, int> > ql[N], qr[N];

ll res[M], ans[M];
int c1[B1][B1], c2[B2][B1], c3[B1][B2], c4[B2][B2], c5[N];
int a[N], buc[N], id[N], rk[N], lst[N], nxt[N], bel2[N], bel3[N], L2[N], L3[N];

int n, m, block, block1, block2, block3;

inline void update(int x, int k) {
    for (int i = 1; i < bel3[x]; ++i)
        for (int j = 1; j < bel3[rk[x]]; ++j)
            c1[i][j] += k;

    for (int i = L3[bel3[x]]; i < bel2[x]; ++i)
        for (int j = 1; j < bel3[rk[x]]; ++j)
            c2[i][j] += k;

    for (int i = 1; i < bel3[x]; ++i)
        for (int j = L3[bel3[rk[x]]]; j < bel2[rk[x]]; ++j)
            c3[i][j] += k;

    for (int i = L3[bel3[x]]; i < bel2[x]; ++i)
        for (int j = L3[bel3[rk[x]]]; j < bel2[rk[x]]; ++j)
            c4[i][j] += k;

    for (int i = L2[bel2[x]]; i < x; ++i)
        if (rk[i] < L2[bel2[rk[x]]])
            c5[i] += k;

    for (int i = L2[bel2[rk[x]]]; i < rk[x]; ++i)
        if (id[i] < x && rk[id[i]] >= L2[bel2[rk[x]]])
            c5[id[i]] += k;
}

inline int query(int x) {
    int a = bel3[x], b = bel2[x], c = bel3[rk[x]], d = bel2[rk[x]];
    return c1[a][c] + c2[b][c] + c3[a][d] + c4[b][d] + c5[x];
}

inline void solve(vector<tuple<int, int, int> > *qry) {
    memset(buc + 1, 0, sizeof(buc));

    for (int i = 1; i <= n; ++i)
        ++buc[a[i]];

    for (int i = 1; i <= n; ++i)
        buc[i] += buc[i - 1];

    for (int i = 1; i <= n; ++i)
        id[rk[i] = buc[a[i]]--] = i;

    memset(buc + 1, 0, sizeof(buc));

    for (int i = 1; i <= n; ++i)
        lst[i] = buc[a[i]], buc[a[i]] = i;

    memset(buc + 1, 0, sizeof(buc));

    for (int i = n; i; --i)
        nxt[i] = buc[a[i]], buc[a[i]] = i;

    memset(c1, 0, sizeof(c1)), memset(c2, 0, sizeof(c2)), memset(c3, 0, sizeof(c3));
    memset(c4, 0, sizeof(c4)), memset(c5, 0, sizeof(c5));

    for (int i = 1; i <= n; ++i) {
        update(i, 1);

        if (lst[i])
            update(lst[i], -1);

        for (auto it : qry[i]) {
            ll sum = 0;

            for (int j = get<0>(it); j <= get<1>(it); ++j)
                sum += query(j) - (nxt[j] < i ? query(nxt[j]) : 0);

            if (get<2>(it) > 0)
                res[get<2>(it)] += sum;
            else
                res[-get<2>(it)] -= sum;
        }
    }
}

signed main() {
    scanf("%d", &n);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);

    scanf("%d", &m), block = sqrt(n);

    for (int i = 1; i <= m; ++i)
        scanf("%d%d", &qry[i].l, &qry[i].r), qry[i].id = i, qry[i].bid = qry[i].l / block;

    sort(qry + 1, qry + 1 + m);

    for (int i = 1, l = 1, r = 0; i <= m; ++i) {
        if (l > qry[i].l)
            ql[r].emplace_back(qry[i].l, l - 1, i), l = qry[i].l;

        if (r < qry[i].r)
            qr[n - l + 1].emplace_back(n - qry[i].r + 1, n - r, i), r = qry[i].r;

        if (l < qry[i].l)
            ql[r].emplace_back(l, qry[i].l - 1, -i), l = qry[i].l;

        if (r > qry[i].r)
            qr[n - l + 1].emplace_back(n - r + 1, n - qry[i].r, -i), r = qry[i].r;
    }

    block1 = pow(n, 0.25), block2 = block1 * block1, block3 = block2 * block1;

    for (int i = 1; i <= n; ++i)
        bel2[i] = (i + block2 - 1) / block2, bel3[i] = (i + block3 - 1) / block3;

    for (int i = n; i; --i)
        L2[bel2[i]] = i, L3[bel3[i]] = bel2[i];

    reverse(a + 1, a + n + 1), solve(qr);

    for (int i = 1; i <= n; ++i)
        a[i] = n + 1 - a[i];

    reverse(a + 1, a + n + 1), solve(ql);

    for (int i = 1; i <= m; ++i)
        res[i] += res[i - 1], ans[qry[i].id] = res[i];

    for (int i = 1; i <= m; ++i)
        printf("%lld\n", ans[i]);

    return 0;
}

P5501 [LnOI2019] 来者不拒,去者不追

给定 \(a_{1 \sim n}\)\(m\) 次询问区间内所有数的价值和,一个数的价值定义为它在区间内的排名乘本身。

\(n, m \le 5 \times 10^5\)\(a_i \le 10^5\)

考虑加入 \(x\) 这个数的贡献:

  • 对于所有大于 \(x\) 的数 \(y\) ,贡献全部增加了 \(y\)
  • 对于 \(x\) 本身,贡献就是 \(x\times t\)\(t\)\([l,r]\) 中比 \(x\) 小的数加 \(1\)

于是莫队二次离线时维护大于 \(x\) 的数的总和和小于 \(x\) 的数的数量即可,注意这里需要用块状数组平衡复杂度。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 5e5 + 7, V = 1e5 + 7;

struct Query {
    int l, r, id, bid;

    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? r < rhs.r : l < rhs.l;
    }
} qry[N];

vector<tuple<int, int, int> > ask[N];

ll s[N], f[N], res[N], ans[N];
int a[N];

int n, m, block;

namespace FK {
const int block = sqrt(V);

ll tags[V], sum[V];
int bel[V], tagc[V], cnt[V];

ll total;

inline void prework() {
    for (int i = 1; i < V; ++i)
        bel[i] = (i + block - 1) / block;

    memset(sum, 0, sizeof(sum)), memset(tags, 0, sizeof(tags));
    memset(cnt, 0, sizeof(cnt)), memset(tagc, 0, sizeof(tagc));
    total = 0;
}

inline void update(int x) {
    total += x;

    for (int i = x; i < V && bel[i] == bel[x]; ++i)
        sum[i] += x, ++cnt[i];

    for (int i = bel[x] + 1; i <= bel[V - 1]; ++i)
        tags[i] += x, ++tagc[i];
}

inline ll query(int x) {
    return total - (tags[bel[x]] + sum[x]) + 1ll * x * (tagc[bel[x - 1]] + cnt[x - 1]);
}
} // namespace FK

signed main() {
    scanf("%d%d", &n, &m), block = sqrt(n);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i), s[i] = s[i - 1] + a[i];

    FK::prework();

    for (int i = 1; i <= n; ++i)
        f[i] = f[i - 1] + FK::query(a[i]), FK::update(a[i]);

    for (int i = 1; i <= m; ++i)
        scanf("%d%d", &qry[i].l, &qry[i].r), qry[i].id = i, qry[i].bid = qry[i].l / block;

    sort(qry + 1, qry + 1 + m);

    for (int i = 1, l = 1, r = 0; i <= m; ++i) {
        if (l > qry[i].l) 
            res[i] -= f[l - 1] - f[qry[i].l - 1], ask[r].emplace_back(qry[i].l, l - 1, i), l = qry[i].l;

        if (r < qry[i].r)
            res[i] += f[qry[i].r] - f[r], ask[l - 1].emplace_back(r + 1, qry[i].r, -i), r = qry[i].r;
        
        if (l < qry[i].l)
            res[i] += f[qry[i].l - 1] - f[l - 1], ask[r].emplace_back(l, qry[i].l - 1, -i), l = qry[i].l;
        
        if (r > qry[i].r)
            res[i] -= f[r] - f[qry[i].r], ask[l - 1].emplace_back(qry[i].r + 1, r, i), r = qry[i].r;
    }

    FK::prework();

    for (int i = 1; i <= n; ++i) {
        FK::update(a[i]);

        for (auto it : ask[i])
            for (int j = get<0>(it); j <= get<1>(it); ++j) {
                if (get<2>(it) > 0)
                    res[get<2>(it)] += FK::query(a[j]);
                else
                    res[-get<2>(it)] -= FK::query(a[j]);
            }
    }

    for (int i = 1; i <= m; ++i)
        ans[qry[i].id] = (res[i] += res[i - 1]) + s[qry[i].r] - s[qry[i].l - 1];

    for (int i = 1; i <= m; ++i)
        printf("%lld\n", ans[i]);

    return 0;
}

P7331 Dream and the Multiverse REMATCH

给定一棵以 \(1\) 为根的外向树,在此基础上加入 \(m\) 条有向边,保证最终的图为 DAG。

\(q\) 次询问,每次给出 \(l, r\) ,求 \(l \le u < v \le r\)\(u\) 能到达 \(v\) 的有序点对 \(u, v\) 的数量。

\(n, q \le 7 \times 10^5\)\(m \le 20\)

考虑莫队二次离线,则需要求前缀到点和点到前缀的有序对数量。

先考虑前缀到点,由于 \(x\) 能到达的点为 \(O(m)\) 个子树并,因此可以扫描线,需要做 \(O(nm)\) 次区间加和 \(O(n \sqrt{n})\) 次单点查。

再考虑点到前缀,则能到达 \(x\) 的点为 \(O(m)\) 条祖先链并,同样需要做 \(O(nm)\) 次区间加和 \(O(n \sqrt{n})\) 次单点查。

考虑多层分块,则可以做到 \(O(B \log_B n) - O(\log_B n)\) 的修改-查询,取 \(B = \frac{\sqrt{n}}{m}\) 即可做到时间复杂度 \(O(n^{1.5} \frac{\log n}{\log n - 2 \log m})\) ,代码里偷懒固定取 \(B = 2^6\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 7e5 + 7, LOGN = 21, M = 21;

struct Graph {
    vector<int> e[N];
    
    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

struct Query {
    int l, r, id, bid;

    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : bid < rhs.bid;
    }
} qry[N];

vector<tuple<int, int, int> > ql[N], qr[N];
pair<int, int> e[M];

ll res[N], ans[N], sl[N], sr[N];
int f[LOGN][N << 1], pos[N], fa[N], in[N], out[N], indeg[N], ord[N], gl[N], gr[N];

int n, m, q, dfstime, cnt;

void dfs(int u) {
    in[u] = ++dfstime, f[0][pos[u] = ++cnt] = u;

    for (int v : G.e[u])
        dfs(v), f[0][++cnt] = u;

    out[u] = dfstime;
}

inline int cmp(const int &a, const int &b) {
    return pos[a] < pos[b] ? a : b;
}

inline int LCA(int x, int y) {
    int l = pos[x], r = pos[y];

    if (l > r)
        swap(l, r);

    int k = __lg(r - l + 1);
    return cmp(f[k][l], f[k][r - (1 << k) + 1]);
}

inline void TopoSort() {
    queue<int> q;
    int cnt = 0;

    for (int i = 1; i <= n; ++i)
        if (!indeg[i])
            q.emplace(i);

    while (!q.empty()) {
        int u = ord[++cnt] = q.front();
        q.pop();

        for (int v : G.e[u])
            if (!(--indeg[v]))
                q.emplace(v);
    }
}

namespace SqrtTree {
int s1[N], s2[N], s3[N];

inline void clear() {
    memset(s1, 0, sizeof(s1)), memset(s2, 0, sizeof(s2)), memset(s3, 0, sizeof(s3));
}

inline void update(int x, int k) {
    for (int i = x >> 6 << 6; i <= x; ++i)
        s1[i] += k;

    for (int i = x >> 12 << 6; i < (x >> 6); ++i)
        s2[i] += k;

    for (int i = 0; i < (x >> 12); ++i)
        s3[i] += k;
}

inline int query(int x) {
    return s1[x] + s2[x >> 6] + s3[x >> 12];
}
} // namespace SqrtTree

inline void updatel(int x) {
    vector<int> vec = {x};

    for (int i = 1; i <= m; ++i)
        if (gr[x] >> i & 1)
            vec.emplace_back(e[i].second);

    sort(vec.begin(), vec.end(), [](const int &a, const int &b) {
        return in[a] < in[b];
    });

    int p = 0;

    for (int it : vec)
        if (out[p] < in[it])
            SqrtTree::update(out[it], 1), SqrtTree::update(in[it] - 1, -1), p = it;
}

inline void updater(int x) {
    vector<int> vec = {x};

    for (int i = 1; i <= m; ++i)
        if (gl[x] >> i & 1)
            vec.emplace_back(e[i].first);

    sort(vec.begin(), vec.end(), [](const int &a, const int &b) {
        return in[a] < in[b];
    });

    for (int it : vec)
        SqrtTree::update(in[it], 1);

    for (int i = 0; i + 1 < vec.size(); ++i)
        SqrtTree::update(in[LCA(vec[i], vec[i + 1])], -1);
}

signed main() {
    scanf("%d%d", &n, &m);

    for (int i = 2; i <= n; ++i)
        scanf("%d", fa + i), G.insert(fa[i], i), ++indeg[i];

    dfs(1);

    for (int j = 1; j <= __lg(cnt); ++j)
        for (int i = 1; i + (1 << j) - 1 <= cnt; ++i)
            f[j][i] = cmp(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);

    for (int i = 1; i <= m; ++i)
        scanf("%d%d", &e[i].first, &e[i].second), G.insert(e[i].first, e[i].second), ++indeg[e[i].second];

    TopoSort();

    for (int i = 1; i <= m; ++i)
        gl[e[i].second] |= 1 << i, gr[e[i].first] |= 1 << i;

    for (int i = 1; i <= n; ++i)
        for (int v : G.e[ord[i]])
            gl[v] |= gl[ord[i]];

    for (int i = n; i; --i)
        for (int v : G.e[ord[i]])
            gr[ord[i]] |= gr[v];

    scanf("%d", &q);
    int block = n / sqrt(q) + 1;

    for (int i = 1; i <= q; ++i)
        scanf("%d%d", &qry[i].l, &qry[i].r), qry[i].id = i, qry[i].bid = qry[i].l / block;

    sort(qry + 1, qry + q + 1);

    for (int i = 1, l = 1, r = 0; i <= q; ++i) {
        if (l > qry[i].l)
            qr[r + 1].emplace_back(qry[i].l, l - 1, -i), l = qry[i].l;

        if (r < qry[i].r)
            ql[l - 1].emplace_back(r + 1, qry[i].r, -i), r = qry[i].r;

        if (l < qry[i].l)
            qr[r + 1].emplace_back(l, qry[i].l - 1, i), l = qry[i].l;

        if (r > qry[i].r)
            ql[l - 1].emplace_back(qry[i].r + 1, r, i), r = qry[i].r;
    }

    for (int i = 1; i <= n; ++i) {
        sl[i] = sl[i - 1] + SqrtTree::query(in[i]), updatel(i);

        for (auto it : ql[i])
            for (int j = get<0>(it); j <= get<1>(it); ++j) {
                if (get<2>(it) > 0)
                    res[get<2>(it)] += SqrtTree::query(in[j]);
                else
                    res[-get<2>(it)] -= SqrtTree::query(in[j]);
            }
    }

    SqrtTree::clear();

    for (int i = n; i; --i) {
        sr[i] = sr[i + 1] + SqrtTree::query(in[i]) - SqrtTree::query(out[i] + 1), updater(i);

        for (auto it : qr[i])
            for (int j = get<0>(it); j <= get<1>(it); ++j) {
                if (get<2>(it) > 0)
                    res[get<2>(it)] += SqrtTree::query(in[j]) - SqrtTree::query(out[j] + 1);
                else
                    res[-get<2>(it)] -= SqrtTree::query(in[j]) - SqrtTree::query(out[j] + 1);
            }
    }

    for (int i = 1, l = 1, r = 0; i <= q; ++i) {
        ans[qry[i].id] = (res[i] += res[i - 1] + (sr[qry[i].l] - sr[l]) + (sl[qry[i].r] - sl[r]));
        l = qry[i].l, r = qry[i].r;
    }

    for (int i = 1; i <= q; ++i)
        printf("%lld\n", ans[i]);

    return 0;
}

莫队配合 bitset

P4688 [Ynoi2016] 掉进兔子洞

\(m\) 次询问,每次询问三个区间,把三个区间中同时出现的数一个个删掉,求最后三个区间剩下的数的个数和,询问独立。

\(n, m \le 10^5\)\(a_i \le 10^9\)

答案为 \(\sum_{i = 1}^3 (r_i - l_i + 1) - 3 \times k\),其中 \(k\) 为三段区间内共有的数的个数,问题转化为求 \(k\)

考虑用莫队维护查询的区间内每个数的个数。对于一次询问,我们将其拆成传统莫队的三个询问:\((l_1, r_1), (l_2, r_2), (l_3, r_3)\) ,分别进行处理。我们要得到 \(k\),需要得到这三个询问中有哪些数、分别几个,然后取它们的交集即可。

注意一个数可能重复出现多次。我们考虑先将初始 \(a\) 数组进行离散化,只记录 \(a\) 数组每一个值对应排序好的数组的编号即可。这样,若干个相同的数便会留出若干个空着的编号,它们代表同一个值。然后使用这个离散化的对应关系用 bitset 存即可。

但是 \(10^5\) 的范围 bitset 存不下,可以把输入的查询分为若干组,每组 \(2\times 10^4\) 个数,对于每一组分别进行处理即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, M = 2e4 + 7;

struct Query {
    int l, r, id, bid;

    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
    }
} qry[N];

bitset<N> res[M], result;

int a[N], cnt[N], ans[M];

int n, m, block;

template <class T = int>
inline T read() {
    char c = getchar();
    bool sign = (c == '-');
    
    while (c < '0' || c > '9')
        c = getchar(), sign |= (c == '-');
    
    T x = 0;
    
    while ('0' <= c && c <= '9')
        x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    
    return sign ? (~x + 1) : x;
}

inline void add(int x) {
    result.set(a[x] + (cnt[a[x]]++));
}

inline void del(int x) {
    result.reset(a[x] + (--cnt[a[x]]));
}

signed main() {
    n = read(), m = read(), block = sqrt(n);
    vector<int> vec;

    for (int i = 1; i <= n; ++i)
        vec.emplace_back(a[i] = read());

    sort(vec.begin(), vec.end());

    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin();

    for (int task = 1; task <= 5; ++task) {
        int q = min(m, 20000), qcnt = 0;

        for (int i = 1; i <= q; ++i) {
            ans[i] = 0, res[i].set();

            for (int j = 1; j <= 3; ++j) {
                qry[++qcnt].l = read(), qry[qcnt].r = read();
                qry[qcnt].id = i, qry[qcnt].bid = qry[qcnt].l / block;
                ans[i] += qry[qcnt].r - qry[qcnt].l + 1;
            }
        }

        sort(qry + 1, qry + 1 + qcnt);
        memset(cnt, 0, sizeof(cnt));
        result.reset();

        for (int i = 1, l = 1, r = 0; i <= qcnt; ++i) {
            while (l > qry[i].l)
                add(--l);

            while (r < qry[i].r)
                add(++r);

            while (l < qry[i].l)
                del(l++);

            while (r > qry[i].r)
                del(r--);

            res[qry[i].id] &= result;
        }

        for (int i = 1; i <= q; ++i)
            printf("%d\n", ans[i] - (int)res[i].count() * 3);

        m = max(m - 20000, 0);
    }

    return 0;
}

P5355 [Ynoi2017] 由乃的玉米田

\(m\) 次询问,每次询问区间内是否能找出两个数使得它们的差/和/积/商为 \(x\)

\(n, m, a_i \le 10^5\)

加减维护一个 bitset 就好了,单次询问复杂度为 \(O(\frac{n}{\omega})\)

积直接暴力枚举因数,单次询问复杂度为 \(O(\sqrt{n})\)

对于商,分类讨论:

  • 如果 \(x \ge \sqrt{n}\) ,那么可以暴力枚举商,然后判断有没有出现即可。因为这个商 \(\le \sqrt{n}\) ,所以复杂度是正确的。
  • 如果 \(x < \sqrt{n}\) ,可以预处理出 \(x \in [1, \sqrt{n})\) 的答案。对于每个 \(x\) 遍历一遍序列,找出每个 \(1 \le i \le n\) 的离 \(i\) 最近且 \(\le i\)\(tp_i\) ,满足 \(a_i\)\(a_{tp_i}\) 的商为 \(x\) ,于是每个询问的答案为 \([l \le tp_r]\) 。这一部分的时间复杂度为 \(O(n \sqrt{n})\)

总时间复杂度为 \(O(\frac{n^2}{\omega} + n \sqrt{n})\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;

struct Query {
    int op, l, r, k, id, bid;

    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
    }
} qry[N];

struct Ask {
    int l, r, id;
};

bitset<N> result1, result2; // i in result1, N - i in result2
vector<Ask> ask[N];

int a[N], cnt[N], pre[N], tp[N];
bool ans[N];

int n, m, block, cntq;

template <class T = int>
inline T read() {
    char c = getchar();
    bool sign = (c == '-');
    
    while (c < '0' || c > '9')
        c = getchar(), sign |= (c == '-');
    
    T x = 0;
    
    while ('0' <= c && c <= '9')
        x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    
    return sign ? (~x + 1) : x;
}

inline void add(int x) {
    if (!cnt[a[x]])
        result1.set(a[x]), result2.set(N - a[x]);

    ++cnt[a[x]];
}

inline void del(int x) {
    --cnt[a[x]];

    if (!cnt[a[x]])
        result1.reset(a[x]), result2.reset(N - a[x]);
}

signed main() {
    n = read(), m = read(), block = sqrt(n) + 1;

    for (int i = 1; i <= n; ++i)
        a[i] = read();

    for (int i = 1; i <= m; ++i) {
        int op = read(), l = read(), r = read(), k = read();

        if (op == 4 && k <= block)
            ask[k].emplace_back((Ask) {l, r, i});
        else
            qry[++cntq] = (Query){op, l, r, k, i, l / block};
    }

    sort(qry + 1, qry + 1 + cntq);

    for (int i = 1, l = 1, r = 0; i <= cntq; ++i) {
        while (l > qry[i].l)
            add(--l);

        while (r < qry[i].r)
            add(++r);

        while (l < qry[i].l)
            del(l++);

        while (r > qry[i].r)
            del(r--);

        if (qry[i].op == 1)
            ans[qry[i].id] = (result1 & (result1 << qry[i].k)).any();
        else if (qry[i].op == 2) {
            ans[qry[i].id] = (result1 & (result2 >> (N - qry[i].k))).any();
        } else if (qry[i].op == 3) {
            for (int j = 1; j * j <= qry[i].k; ++j)
                if (!(qry[i].k % j) && result1.test(j) && result1.test(qry[i].k / j)) {
                    ans[qry[i].id] = true;
                    break;
                }
        } else {
            for (int j = 1; j * qry[i].k < N; ++j)
                if (result1.test(j) && result1.test(j * qry[i].k)) {
                    ans[qry[i].id] = true;
                    break;
                }
        }
    }

    for (int i = 1; i <= block; ++i) {
        if (ask[i].empty())
            continue;

        memset(pre, 0, sizeof(pre));
        memset(tp, 0, sizeof(tp));

        for (int j = 1, pos = 0; j <= n; ++j) {
            pre[a[j]] = j;

            if (a[j] * i < N)
                pos = max(pos, pre[a[j] * i]);

            if (!(a[j] % i))
                pos = max(pos, pre[a[j] / i]);

            tp[j] = pos;
        }

        for (Ask it : ask[i])
            ans[it.id] = (it.l <= tp[it.r]);
    }

    for (int i = 1; i <= m; ++i)
        puts(ans[i] ? "yuno" : "yumi");

    return 0;
}
posted @ 2024-07-20 15:54  wshcl  阅读(32)  评论(0)    收藏  举报