首页 > 技术文章 > 【数据结构】莫队算法

purinliang 2021-01-03 17:26 原文

int n;

namespace MoAlgorithm {

    static const int MAXM = 3e5 + 10;
    static const int MAXN = 3e5 + 10;

    int n, m, BLOCK;

    struct Node {
        int l, r, id;

        bool operator<(const Node &node) const {
            if (l / BLOCK != node.l / BLOCK)
                return l < node.l;
            if ((l / BLOCK) & 1)
                return r < node.r;
            return r > node.r;
        }
    } node[MAXM];

    int ans[MAXM];

    int L, R;

    int a[MAXN];
    int cnt[MAXN];
    int cntcnt[MAXN];
    int curans;

    void add(int x) {
        x = a[x];
        --cntcnt[cnt[x]];
        if (cnt[x] == curans)
            ++curans;
        ++cnt[x];
        ++cntcnt[cnt[x]];
    }

    void sub(int x) {
        x = a[x];
        --cntcnt[cnt[x]];
        if (cnt[x] == curans && cntcnt[cnt[x]] == 0)
            --curans;
        --cnt[x];
        ++cntcnt[cnt[x]];
    }

    void Solve() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i)
            cnt[i] = 0;
        cntcnt[0] = n;
        for (int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        BLOCK = (int) ceil(pow(n, 0.5));
        for (int i = 1; i <= m; ++i) {
            scanf("%d%d", &node[i].l, &node[i].r);
            node[i].id = i;
        }
        sort(node + 1, node + 1 + m);
        L = 1, R = 0, curans = 0;
        for (int i = 1; i <= m; ++i) {
            while (L > node[i].l)
                --L, add(L);
            while (R < node[i].r)
                ++R, add(R);
            while (L < node[i].l)
                sub(L), ++L;
            while (R > node[i].r)
                sub(R), --R;
            int len = R - L + 1;
            if (curans <= (len + 1) / 2)
                ans[node[i].id] = 1;
            else
                ans[node[i].id] = 2;
        }
        for (int i = 1; i <= m; ++i)
            printf("%d\n", ans[i]);
    }

}

CF617E - XOR and Favorite Number

统计[L,R]内有多少对(i,j)满足ij之间的所有元素异或起来等于某个常数k。这里的区间[L,R]中夹着的异或前缀和是[p[L-1],p[R]]这么多个,所以一开始初始化的时候虽然是空集但是是有p[0]在,往左拓展区间的时候是把L-1加进来,往右的时候正常把R加进来。

namespace MoAlgorithm {

    static const int MAXM = 1e5 + 10;
    static const int MAXN = 1e5 + 10;

    int n, m, BLOCK;

    struct Node {
        int l, r, id;
        bool operator<(const Node &node) const {
            if (l / BLOCK != node.l / BLOCK)
                return l < node.l;
            if ((l / BLOCK) & 1)
                return r < node.r;
            return r > node.r;
        }
    } node[MAXM];
    ll ans[MAXM];

    int L, R;
    ll curans;

    int a[MAXN], p[MAXN];
    int k, cnt[(1 << 20)];

    void add(int x) {
        curans += cnt[k ^ p[x]];
        ++cnt[p[x]];
    }

    void sub(int x) {
        --cnt[p[x]];
        curans -= cnt[k ^ p[x]];
    }

    void Solve() {
        ms(cnt);
        scanf("%d%d%d", &n, &m, &k);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            p[i] = p[i - 1] ^ a[i];
        }
        BLOCK = (int)ceil(pow(n, 0.5));
        for (int i = 1; i <= m; ++i) {
            scanf("%d%d", &node[i].l, &node[i].r);
            node[i].id = i;
        }
        sort(node + 1, node + 1 + m);
        L = 1, R = 0, curans = 0, cnt[p[0]] = 1;
        for (int i = 1; i <= m; ++i) {
            while (L > node[i].l)
                --L, add(L - 1);
            while (R < node[i].r)
                ++R, add(R);
            while (L < node[i].l)
                sub(L - 1), ++L;
            while (R > node[i].r)
                sub(R), --R;
            ans[node[i].id] = curans;
        }
        for (int i = 1; i <= m; ++i)
            printf("%lld\n", ans[i]);
    }

}

https://codeforces.com/contest/86/problem/D
https://codeforces.com/contest/1288/problem/E
https://codeforces.com/contest/877/problem/F

带修改莫队:增加一维时间T,代表在进行了T次修改之后才进行该次询问。

树上莫队:按树的欧拉序(进入和退出某个节点都记录)一维化成一个序列,fst[x],lst[x],查询[x,y]路径的信息(不妨设fst[x]<fst[y]),当x和y是祖先/后代关系时(fst[x]<fst[y]<lst[y]<fst[x])查询[fst[x],fst[y]]就是x和y之间的节点。否则在遍历x退出之后到进入y的过程中走过的就是[x,y]的路径[lst[x],fst[y]],当然lca(x,y)也在路径上但是不出现在上述区间内,也另外加进去。

推荐阅读