C++怎么实现莫队算法_C++离线区间查询优化【算法】

3次阅读

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

C++怎么实现莫队算法_C++离线区间查询优化【算法】

莫队算法必须手动排序查询,别指望 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,能减少右端点抖动
  • 别把 lr 存成 shortunsigned,排序时符号扩展或溢出会引发诡异错位

add()remove() 必须严格可逆,且不能依赖全局状态快照

莫队靠指针游走更新答案,每次只增/删一个位置。如果 add(i) 里写了 ans += cnt[a[i]]++,那 remove(i) 就得是 ans -= --cnt[a[i]],顺序反了结果就错。

常见错误现象:

立即学习C++免费学习笔记(深入)”;

  • 删的时候用了 cnt[a[i]]-- 再减,但此时 cnt 已经变了,减的是旧值
  • mapunordered_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]++
  • 输入 n01sqrt(n)0,块大小为 0 导致除零崩溃

性能影响:用 vector 而非裸数组时,确保 reserve() 避免多次扩容;OFFSET 别设太大,否则内存占用陡增。

事情说清了就结束

text=ZqhQzanResources