洛谷 P3901:数列找不同 ← 基础莫队算法
【题目来源】https://www.luogu.com.cn/problem/P3901【题目描述】现有数列 A1A2…ANQ 个询问 (LiRi)询问 A_LiA_{Li1}…A_Ri 是否互不相同。【输入格式】第一行两个整数 NQ。第二行N 个整数 A1A2…AN。接下来 Q 行每行两个整数 LiRi。【输出格式】对每个询问输出一行Yes 或 No。【输入样例】4 21 2 3 21 32 4【输出样例】YesNo【数据范围】对于 50% 的数据N,Q≤10^3。对于 100% 的数据1≤N,Q≤10^51≤Ai≤N1≤Li≤Ri≤N。【算法分析】● 基础莫队算法Mos Algorithm是一种经分块排序优化的增量暴力算法。它不依赖线段树、树状数组等高级数据结构而是将分块思想与暴力枚举巧妙融合填补了传统暴力算法在处理大规模离线区间统计问题时的效率空白。显然基础莫队算法是一种暴力算法但它不是 O(n²) 的原始暴力而是用分块排序把原始暴力摊成 O(n√n) 的暴力即所谓“优雅的暴力”。其绝大部分时间开销来源于指针移动所触发的单点增删与答案更新操作。● 基础莫队算法中的「指针移动」就是通过不断调整区间左右端点向区间新增或移除元素实现答案的增量更新直至抵达当前查询的目标区间。● 基础莫队算法维护两个区间指针 le、ri代表当前已维护好答案的区间 [le, ri]。在处理经过分块排序的查询序列时1对于每条查询 [qle, qri]算法不会对每个查询独立重新计算而是通过不断移动左右指针le←le±1、ri←ri±1将当前区间逐步调整为目标区间利用相邻查询的重叠部分避免重复计算。2每移动一步指针只执行一次单点增/删操作将一个新元素加入区间或从区间移除一个旧元素并同步更新当前答案。加入和删除的操作逻辑互为逆运算这是莫队能够高效维护答案的关键。3指针移动遵循先扩后缩的稳健原则优先移动右指针 ri 向右扩展再移动左指针 le 调整左边界避免指针交错导致区间无效。● 基础莫队算法与分块的联系基础莫队算法是基于分块思想构建的离线区间查询优化算法分块为其提供排序依据与复杂度保障两者关系可概括为基础莫队算法离线处理分块排序暴力转移。1离线处理预先载入所有查询不支持在线即时回答。2分块排序将原序列按 √n 大小分块对查询进行多级排序。3暴力转移移动左右指针、单点增删元素、增量更新区间答案的过程。●奇偶性排序基础莫队算法的核心思想是借助分块策略重排查询规范指针移动次序把总移动代价限制在 O(n√n)。朴素分块排序难以规避跨块时指针长途折返奇偶排序是重要的常数优化技巧用以减少这类无效移动。具体策略如下1首先将长度为 n 的序列分成 sqrt(n) 个块2然后将所有询问按左端点 L 所在块的编号为第一关键字排序。对于左端点位于同一块内的询问采用奇偶性排序决定右端点 R 的次序。即若该块编号为奇数则右端点 R 从小到大排序若该块编号为偶数则右端点 R 从大到小排序。● 为什么不同块内无需奇偶优化1基础莫队算法的核心逻辑是“先按块定序再按右端点调序”。不同块之间的排序仅依赖左端点所在块的编号这一关键字不引入任何翻转策略。2在莫队算法的标准实现中奇偶优化或称翻转排序的作用域严格限定在同一块内作为第二关键字的生成规则奇数块右端点升序偶数块右端点降序。跨块层级不应也不能应用奇偶优化。● 竞赛标准写法先扩张再收缩保证不会在空区间删除元素。while(riq[i].ri) add(ri); while(leq[i].le) add(--le); while(riq[i].ri) del(ri--); while(leq[i].le) del(le);【算法代码】#include bits/stdc.h using namespace std; typedef long long LL; const int N1e55; int a[N],cnt[N]; bool ans[N]; LL cur; int block,n,m; struct Node { int le,ri,idx; } q[N]; bool cmp(Node a,Node b) { if(a.le/block ! b.le/block) { return a.leb.le; } //odd-even optimization if(a.le/block 1) return a.rib.ri; else return a.rib.ri; } void add(int x) { int vala[x]; cur2*cnt[val]1; cnt[val]; } void del(int x) { int vala[x]; cnt[val]--; cur-2*cnt[val]1; } int main() { ios::sync_with_stdio(0); cin.tie(0); cinnm; for(int i1; in; i) { cina[i]; } blocksqrt(n); for(int i0; im; i) { cinq[i].leq[i].ri; q[i].idxi; } sort(q,qm,cmp); int le1,ri0; for(int i0; im; i) { while(riq[i].ri) add(ri); while(leq[i].le) add(--le); while(riq[i].ri) del(ri--); while(leq[i].le) del(le); int lenq[i].ri-q[i].le1; ans[q[i].idx](curlen); } for(int i0; im; i) { if(ans[i]) coutYes\n; else coutNo\n; } return 0; } /* in: 4 2 1 2 3 2 1 3 2 4 out: Yes No */【参考文献】https://blog.csdn.net/hnjzsyjyj/article/details/163114366https://blog.csdn.net/hnjzsyjyj/article/details/163143122https://blog.csdn.net/hnjzsyjyj/article/details/138976338https://blog.csdn.net/Tudou_Pika/article/details/129225941