莫队笔记

莫队的主要思想是将询问离线,并通过分块合理利用询问之间的公共信息,高效率地求解问题。

普通莫队

P3901 数列找不同

考虑记 $c_i$ 表示 $i$ 的出现次数,并在移动左右端点的同时维护 $c$ 数组以及 $res$。

于是就做完了。

注意正确的顺序应该是 l--,r--,r++,l++,可以奇偶排序优化。

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

inline int read() {
    bool f = 0;
    char ch = getchar_unlocked();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = 1;
        ch = getchar_unlocked();
    }
    int x = 0;
    while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar_unlocked();
    return f ? -x : x;
}

int n, q, a[N], B;
struct node {
    int l, r;
    int id;
} b[N];
inline bool cmp(node a, node b) {
    if (a.l / B != b.l / B) return a.l < b.l;
    if ((a.l / B) & 1) return a.r < b.r;
    return a.r > b.r;
}

bool ans[N];
int c[N], cnt = 0;
inline void add(int x) { c[a[x]]++, cnt += (c[a[x]] == 2); }
inline void del(int x) { c[a[x]]--, cnt -= (c[a[x]] == 1); }

int main() {
    n = read(), q = read(), B = sqrt(n);
    for (int i = 1; i <= n; i++) a[i] = read(); 
    for (int i = 1; i <= q; i++) b[i].l = read(), b[i].r = read(), b[i].id = i;
    sort(b + 1, b + 1 + q, cmp);
    int l = 1, r = 1; c[a[1]]++;
    for (int i = 1; i <= q; i++) {
        while (l > b[i].l) add(--l);
        while (r > b[i].r) del(r--);
        while (r < b[i].r) add(++r);
        while (l < b[i].l) del(l++);
        ans[b[i].id] = (cnt == 0);
    }
    for (int i = 1; i <= q; i++) puts(ans[i] ? "Yes" : "No");
    return 0;
}

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

P2709 小B的询问

树上莫队

考虑把括号序列整出来(为什么大部分题解都说这是欧拉序??)然后在上面干一些神秘的事情。

COT2 - Count on a tree II

分类讨论 $(u,v)$ 是否有祖孙关系,这里令 $in_u \lt in_v$。

若是祖孙关系,则 $[in_u,in_v]$,否则 $[out_u,in_v]$。

然后正常莫队。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 15, M = N << 1;
int n, q, B, a[N], b[N], n1;
int h[N], e[M], ne[M], idx = 0;
void addedge(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }

int sz[N], son[N], fa[N], dep[N];
void dfs1(int u, int father) {
    sz[u] = 1, fa[u] = father, dep[u] = dep[father] + 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (v == father) continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (!son[u] || (sz[son[u]] < sz[v])) son[u] = v;
    }
}
int in[N], out[N], tot = 0;
int top[N];
int dfn[N << 1];
void dfs2(int u, int t) {
    dfn[++tot] = u, in[u] = tot;

    top[u] = t;
    if (son[u]) dfs2(son[u], t);
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
    dfn[++tot] = u, out[u] = tot;
}

int lca(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    return u;
}

struct node {
    int l, r, id;
    int up; //lca
} p[N];
bool cmp(node a, node b) {
    if (a.l / B != b.l / B) return a.l < b.l;
    if ((a.l / B) & 1) return a.r < b.r;
    return a.r > b.r;
}

int ans[N];
bool st[N]; //异或对消
int c[N], res = 0;
void add(int x) { c[a[x]]++, res += (c[a[x]] == 1); }
void del(int x) { c[a[x]]--, res -= (c[a[x]] == 0); }
void get(int x) {
    if (st[dfn[x]]) del(dfn[x]);
    else add(dfn[x]);
    st[dfn[x]] ^= 1;
}

int main() {
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i], h[i] = -1;
    //离散化
    sort(b + 1, b + 1 + n), n1 = unique(b + 1, b + 1 + n) - b - 1;
    for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + 1 + n1, a[i]) - b;
    // for (int i = 1; i <= n; i++) cout << a[i] << ' '; puts("");
    //build tree
    for (int i = 1, u, v; i < n; i++) {
        scanf("%d%d", &u, &v);
        addedge(u, v), addedge(v, u);
    }

    dfs1(1, 0), dfs2(1, 1);
    B = sqrt(tot);

    for (int i = 1, u, v; i <= q; i++) {
        scanf("%d%d", &u, &v);
        if (in[u] > in[v]) swap(u, v);
        int l = lca(u, v);
        if (l == u) {   //u is v's grandfather
            //in[u] -> in[v]
            p[i] = (node){in[u], in[v], i, 0};
        } else {    //u -> l -> v
            //out[u] -> in[v]
            p[i] = (node){out[u], in[v], i, l};
        }
    }
    sort(p + 1, p + 1 + q, cmp);
    int l = 1, r = 1; c[a[1]]++, st[1] = 1, res++;
    for (int i = 1; i <= q; i++) {
        while (r < p[i].r) get(++r);
        while (r > p[i].r) get(r--);
        while (l < p[i].l) get(l++);
        while (l > p[i].l) get(--l);
        int up = p[i].up;
        if (up) c[a[up]]++, res += (c[a[up]] == 1);
        ans[p[i].id] = res;
        if (up) c[a[up]]--, res -= (c[a[up]] == 0);
    }
    for (int i = 1; i <= q; i++) printf("%d\n", ans[i]);
    return 0;
}

带修莫队

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

多一维时间罢了,注意先修改 l,r 再修改 t

注意在移动时间轴的时候要特判在区间内再修改答案。

#include <bits/stdc++.h>
using namespace std;
const int N = 140015, M = 1e6 + 15;
int n, q, a[N], B, ans[N];
int cntC = 0, cntQ = 0;

pair<int, int> change[N];
struct node {
    int l, r, t, id;
} p[N << 1];
bool cmp(node a, node b) {
    if ((a.l - 1) / B != (b.l - 1) / B) return a.l < b.l;
    if ((a.r - 1) / B != (b.r - 1) / B) return a.r < b.r;
    return a.t < b.t;
}

int c[M], res = 0;
void add(int x) { c[a[x]]++, res += (c[a[x]] == 1); }
void del(int x) { c[a[x]]--, res -= (c[a[x]] == 0); }
void work(int l, int r, int t) {
    int pos = change[t].first, val = change[t].second;
    if (l <= pos && pos <= r) {
        c[a[pos]]--, res -= (c[a[pos]] == 0);
        c[val]++, res += (c[val] == 1);
    }
    swap(a[pos], change[t].second);
}

int main() {
    scanf("%d%d", &n, &q), B = pow(n, 0.666666);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= q; i++) {
        char opt; scanf(" %c", &opt);
        if (opt == 'R') {
            int x, y; scanf("%d%d", &x, &y);
            change[++cntC] = make_pair(x, y);
        } else {    //'Q'
            int l, r; scanf("%d%d", &l, &r);
            p[++cntQ] = (node){l, r, cntC, cntQ};
        }
    }
    sort(p + 1, p + 1 + cntQ, cmp);
    int l = 1, r = 1, t = 0;
    c[a[1]]++, res = 1;
    for (int i = 1; i <= cntQ; i++) {
        while (r > p[i].r) del(r--);
        while (r < p[i].r) add(++r);
        while (l > p[i].l) add(--l);
        while (l < p[i].l) del(l++);
        while (t < p[i].t) work(l, r, ++t);
        while (t > p[i].t) work(l, r, t--);
        ans[p[i].id] = res;
    }
    for (int i = 1; i <= cntQ; i++) printf("%d\n", ans[i]);
    return 0;
}

回滚莫队

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

左端点在同一块内则右端点升序。

这样右端点不断往右走,左端点不断在块内反复横跳,可以保证复杂度。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 15, INF = 0x3f3f3f3f;
int n, n1, q, a[N], b[N], B;
int ans[N];

struct node {
    int l, r, id;
} p[N];
bool cmp(node a, node b) {
    if ((a.l - 1) / B != (b.l - 1) / B) return a.l < b.l;
    return a.r < b.r;
}

int Max[N], Min[N];
int pmx[N], pmn[N];
int get(int l, int r) {
    int res = 0;
    for (int i = r; i >= l; i--) Max[a[i]] = max(Max[a[i]], i), res = max(res, Max[a[i]] - i);
    for (int i = l; i <= r; i++) Max[a[i]] = 0;
    return res;
}

bool clr[N];
int res = 0, lst = 0;
void add(int x, bool need) {    //need==1 ---> 需要删除
    if (need && !clr[a[x]]) clr[a[x]] = 1, pmx[a[x]] = Max[a[x]], pmn[a[x]] = Min[a[x]];
    Max[a[x]] = max(Max[a[x]], x), Min[a[x]] = min(Min[a[x]], x);
    res = max(res, Max[a[x]] - Min[a[x]]);
}
void del(int x) {
    if (clr[a[x]]) {
        Max[a[x]] = pmx[a[x]], Min[a[x]] = pmn[a[x]];
        pmx[a[x]] = pmn[a[x]] = 0;
        clr[a[x]] = 0;
    }
}

int main() {
    scanf("%d", &n), B = sqrt(n);
    for (int i = 1; i <= n; i++) Max[i] = 0, Min[i] = INF;

    for (int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i];
    sort(b + 1, b + 1 + n); n1 = unique(b + 1, b + 1 + n) - b - 1;
    for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + 1 + n1, a[i]) - b;

    scanf("%d", &q);
    for (int i = 1; i <= q; i++) scanf("%d%d", &p[i].l, &p[i].r), p[i].id = i;
    sort(p + 1, p + 1 + q, cmp);
    int i = 1, j = 1;
    while (i <= q) {
        lst = res = 0;
        for (int v = 1; v <= n1; v++) Max[v] = 0, Min[v] = INF;

        while (j < q && (p[i].l - 1) / B == (p[j + 1].l - 1) / B) j++;
        int k = i, R = ((p[i].l - 1) / B + 1) * B;

        while (k <= j && p[k].r <= R) ans[p[k].id] = get(p[k].l, p[k].r), k++;
        int l = R + 1, r = R;
        while (k <= j) {
            while (r < p[k].r) add(++r, 0);
            lst = res;
            while (l > p[k].l) add(--l, 1);
//          cout << '\t' << l << ' ' << r << ' ' << p[k].id << endl;
            ans[p[k].id] = res;
            while (l < R + 1) del(l++);
            res = lst;
            k++;
        }
        i = j + 1;
    }
    for (int k = 1; k <= q; k++) printf("%d\n", ans[k]);
    return 0;
}

歴史の研究

同理。

莫队二次离线等其它知识点

idk, idk, idk.

posted @ 2025-01-22 17:06  Conan15  阅读(10)  评论(0)    收藏  举报  来源