莫队学习笔记

莫队介绍:莫队算法初探 - 洛谷日报莫队算法——从入门到黑题 - 博客园.

普通莫队

1. SP3267 DQUERY - D-query

vjudge link

普通莫队模板,要注意的是,分块大小 \(T\) ,当 \(n\)\(m\) 同阶时,一般取 \(T=\sqrt n\) ,当 \(m\) 特别大时,取 \(T=\frac{n}{\sqrt m}\) 更优,具体见莫队算法初探 - 洛谷日报.

#include<bits/stdc++.h>
using namespace std;

inline int read(){
    int x = 0, w = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0'; ch = getchar();}
    return x * w;
}
inline void write(int x){
    if(x < 0){putchar('-'); x = -x;}
    if(x / 10) write(x / 10);
    putchar(x % 10 + '0');
}

const int N = 30010;
const int M = 200010;
int bel[N], a[N], cnt[1000010], ans[M];
struct node{
    int l, r, i;
    bool operator<(node t){return bel[l] ^ bel[t.l] ? bel[l] < bel[t.l] : (bel[l] & 1 ? r < t.r : r > t.r);}
}q[M];
int n, m, t, now = 0;

inline void add(int i){now += !cnt[a[i]]++;}
inline void del(int i){now -= !--cnt[a[i]];}

int main(){
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    m = read(), t = n / sqrt(m);
    for(int i = 1; i <= n; ++i) bel[i] = (i + t - 1) / t;
    for(int i = 1; i <= m; ++i) q[i].l = read(), q[i].r = read(), q[i].i = i;
    sort(q + 1, q + m + 1);
    int l = 1, r = 0;
    for(int i = 1; i <= m; ++i){
        while(l < q[i].l) del(l++);
        while(l > q[i].l) add(--l);
        while(r < q[i].r) add(++r);
        while(r > q[i].r) del(r--);
        ans[q[i].i] = now;
    }
    for(int i = 1; i <= m; ++i) write(ans[i]), putchar('\n');
    return 0;
}

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

莫队简单运用,首先要知道这个概率怎么算,设序列长度为 \(n\) ,数 \(i\) 出现 \(cnt_i\) 次,那么选取两个数的总方案数:

\[\binom{n}{2}=\frac{n(n-1)}{2} \]

对于每个 \(i\) ,合法方案数:

\[\binom{cnt_i}{2}=\frac{cnt_i(cnt_i-1)}{2} \]

故选取两个数相同的概率:

\[\frac{\sum_{i}\frac{cnt_i(cnt_i-1)}{2}}{\frac{n(n-1)}{2}}=\frac{\sum_{i}cnt_i(cnt_i-1)}{n(n-1)} \]

考虑莫队,在原询问区间的基础上加一个数 \(i\) ,维护合法方案数,分子中第 \(i\) 项变为 \(cnt_i(cnt_i+1)=cnt_i(cnt_i-1)+2cnt_i\) ,减一个数 \(i\) ,变为 \((cnt_i-1)(cnt_i-2)=cnt_i(cnt_i-1)-2(cnt_i-1)\) ,这里 \(cnt_i\) 是原来的,所以加一个数就等于加上 \(2cnt_i\) ,减一个数就等于减去 \(2(cnt_i-1)\) ,注意开 long long.

AC Record

3. P2709 【模板】莫队 / 小B的询问

模板题,增减用完全平方公式 \((x\pm 1)=x^2\pm 2x+1\).

AC Record

4. P4462 [CQOI2018] 异或序列

双倍经验:CF617E XOR and Favorite Number

看到区间异或和自然想到异或前缀和,令 \(s_i=\oplus_{j=1}^i a_i\) ,则区间 \([l,r]\) 的异或和为 \(s_r\oplus s_{l-1}\) ,如果增加了 \(r\) ,判断 \(l-1\) 是否合法,即判断 \(s_r\oplus s_{l-1}=k\) ,所以就是要找 \(s_{l-1}=s_r\oplus k\) ,开一个桶 \(cnt\) 记录 \(s\) ,每次增减找 \(cnt_{s_r\oplus k}\) 即可,还有几个要注意的点:

  • 因为维护前缀和,所以询问是对于 \([l-1,r]\) 的,左端点减一.

  • 因为 \(l\le r\)\(l-1\)\(r\) 不同,当 \(k=0\) 时避免多减,所以增加先算答案再算 \(cnt\) ,减少相反.

  • 异或前缀和值可能比 \(n\) 大,\(cnt\) 开大一点.

AC Record for P4462

AC Record for CF617E

5. P3709 大爷的字符串题

双倍经验:P1997 faebdc 的烦恼

我是没有语文能力的小丑,使答案最小,可以每次取出一个最长上升子序列,发现序列个数就是区间众数的出现次数,存一下数 \(i\) 的出现次数 \(cnt_i\) 和出现 \(i\) 次的数的数量 \(f_i\) ,增加一个数,先更新 \(cnt_i\)\(f_{cnt_i}\) ,再和当前答案取最大值;减少一个数,如果这个数是众数且出现次数唯一,答案减少,更新 \(cnt_i\)\(f_{cnt_i}\).

AC Record for P3709

AC Record for P1997

6. CF877F Ann and Books

和异或序列差不多,但需要卡常,注意朴素前缀和是有顺序的,另外这题离散化较为复杂,unordered_map 常数太大过不去,可以使用 pb_dsgp_hash_table ,还有我为什么怎么算 \(T\) 都过不去,推荐使用固定块长 \(T=500\).

AC Record

值域分块

故名思意,对数的值域进行分块,数列分块是维护数列中的数,而值域分块则是维护数列中值的出现次数,与莫队结合通常是莫队移动指针,值域分块查询,可以做到 \(O(1)\) 修改,\(O(\sqrt n)\) 查询,可以减少修改常数。

\(cnt_i\) 为数列中数值 \(i\) 的出现次数,对 \(cnt\) 进行分块,另开一个数组记录要查询的答案,这样,对于查询全局第 \(k\) 小或某个值域范围内的数的个数等问题,我们可以利用值域分块快速定位。

posted @ 2026-02-22 15:52  EvanYow  阅读(0)  评论(0)    收藏  举报