莫队学习笔记
莫队介绍:莫队算法初探 - 洛谷日报、莫队算法——从入门到黑题 - 博客园.
普通莫队
1. SP3267 DQUERY - D-query
普通莫队模板,要注意的是,分块大小 \(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\) 次,那么选取两个数的总方案数:
对于每个 \(i\) ,合法方案数:
故选取两个数相同的概率:
考虑莫队,在原询问区间的基础上加一个数 \(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.
3. P2709 【模板】莫队 / 小B的询问
模板题,增减用完全平方公式 \((x\pm 1)=x^2\pm 2x+1\).
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\) 开大一点.
5. P3709 大爷的字符串题
双倍经验:P1997 faebdc 的烦恼
我是没有语文能力的小丑,使答案最小,可以每次取出一个最长上升子序列,发现序列个数就是区间众数的出现次数,存一下数 \(i\) 的出现次数 \(cnt_i\) 和出现 \(i\) 次的数的数量 \(f_i\) ,增加一个数,先更新 \(cnt_i\) 和 \(f_{cnt_i}\) ,再和当前答案取最大值;减少一个数,如果这个数是众数且出现次数唯一,答案减少,更新 \(cnt_i\) 和 \(f_{cnt_i}\).
6. CF877F Ann and Books
和异或序列差不多,但需要卡常,注意朴素前缀和是有顺序的,另外这题离散化较为复杂,unordered_map 常数太大过不去,可以使用 pb_ds 的 gp_hash_table ,还有我为什么怎么算 \(T\) 都过不去,推荐使用固定块长 \(T=500\).
值域分块
故名思意,对数的值域进行分块,数列分块是维护数列中的数,而值域分块则是维护数列中值的出现次数,与莫队结合通常是莫队移动指针,值域分块查询,可以做到 \(O(1)\) 修改,\(O(\sqrt n)\) 查询,可以减少修改常数。
记 \(cnt_i\) 为数列中数值 \(i\) 的出现次数,对 \(cnt\) 进行分块,另开一个数组记录要查询的答案,这样,对于查询全局第 \(k\) 小或某个值域范围内的数的个数等问题,我们可以利用值域分块快速定位。

浙公网安备 33010602011771号