莫队算法需手动排序查询:按左端点分块、右端点奇偶优化单调移动;块大小取 max(1, (int)sqrt(n));add/remove 必须严格可逆;频次用 vector 而非 map;带修需用带修莫队,块大小为 n^(2/3)。

莫队算法必须手动排序查询,别指望 std::sort 默认就能对
莫队的核心是重排查询顺序,让左端点按块分组、右端点在块内单调移动。如果只写 std::sort(qs.begin(), qs.end()),默认按字典序比较(先比 l 再比 r),会导致右端点来回跳,复杂度退化到 O(nq)。
实操建议:
- 定义块大小
block = sqrt(n),注意用整型上取整,避免除零或块数为 0;block = max(1, (int)sqrt(n)) - 排序谓词里先按
l / block分块,再按r单调——但奇偶优化更稳:(l / block) & 1 ? r : -r,能减少右端点抖动 - 别把
l和r存成short或unsigned,排序时符号扩展或溢出会引发诡异错位
add() 和 remove() 必须严格可逆,且不能依赖全局状态快照
莫队靠指针游走更新答案,每次只增/删一个位置。如果 add(i) 里写了 ans += cnt[a[i]]++,那 remove(i) 就得是 ans -= --cnt[a[i]],顺序反了结果就错。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 删的时候用了
cnt[a[i]]--再减,但此时cnt已经变了,减的是旧值 - 用
map或unordered_map维护频次,插入/删除开销大,O(log n)或均摊O(1)但常数高,实际比数组慢 3–5 倍 - 在
add()里修改了别的数组(比如前缀和),但remove()没还原,后续查询答案漂移
推荐:频次数组一律用 vector<int></int>,下标范围确认不越界;操作只读/写当前下标,不碰其他位置。
离线意味着不能边查边改数组,所有修改必须发生在 add()/remove() 里
如果你的题目带“单点修改”,比如「区间众数 + 单点赋值」,纯莫队扛不住——它本质是静态离线算法。强行加修改会破坏查询顺序的连续性,复杂度崩盘。
使用场景判断:
- 只有查询?→ 莫队合适,
O((n + q) * sqrt(n))可接受 - 有修改且修改少(≤ 100 次)?→ 考虑「带修莫队」:把查询带上时间戳,排序维度变成
(l/block, r/block, time),但代码量翻倍、调试极难 - 修改频繁或强制在线?→ 直接换主席树 / 线段树 / 根号分治,别硬套莫队
参数差异:带修莫队的块大小通常取 n^(2/3),不是 sqrt(n),否则时间戳维度压不住。
边界和数据类型容易翻车,尤其 long long 和负数下标
莫队中间变量如 ans、计数器累加值,经常溢出。比如求区间平方和,a[i] 是 1e5,100 个数加起来就超 int。
容易踩的坑:
-
ans类型没设成long long,但累加式含乘法(如ans += 1LL * cnt[x] * x),局部转了long long,总和仍截断 - 数组值域含负数,又用值当数组下标(比如
cnt[a[i]]++),直接越界访问——必须做偏移,如cnt[a[i] + OFFSET]++ - 输入
n是0或1,sqrt(n)得0,块大小为 0 导致除零崩溃
性能影响:用 vector 而非裸数组时,确保 reserve() 避免多次扩容;OFFSET 别设太大,否则内存占用陡增。
事情说清了就结束










