莫队
发明者莫涛,用于比较暴力的进行区间操作
以简短的框架、简单易记的板子和优秀的复杂度闻名于世
然而由于莫队算法应用的毒瘤,很多可做的莫队模板题都有着较高的难度评级,令很多初学者望而却步
然而,如果真正理解了莫队的算法原理,那么用起来还是很简单的
前置芝士
莫队算法还是比较独立的。不过你还是得了解了解以下的一些知识:
1、分块的基本思想
2、(STL) 中 (sort) 的用法(手写 (cmp) 函数或重载运算符实现结构体的多关键字排序)
3、各种卡常技巧
4、倍增/树剖 求LCA(树上莫队所需)
5、数值离散化(用于应付很多题目)
基础实现
莫队算法优化的核心是分块和排序
我们将大小为 (n) 的序列分为 (sqrt{n}) 个块,从 (1) 到 (sqrt{n}) 编号,然后根据这个对查询区间进行排序
一种方法是把查询区间按照左端点所在块的序号排个序,如果左端点所在块相同,再按右端点排序
排完序后我们再进行左右指针跳来跳去的操作,虽然看似没多大用,但带来的优化实际上极大
int Cmp(node x,node y){return x.l/sqrtn==y.l/sqrtn?x.r<y.r:x.l/sqrtn<y.l/sqrtn;}
关于左右指针的移动,要确定当前的答案是否已经被统计,是否要保留
for(rr int i=1;i<=q;i++){
while(L>Ask[i].l){Add(--L);}
while(R<Ask[i].r){Add(++R);}
while(L<Ask[i].l){Del(L++);}
while(R>Ask[i].r){Del(R--);}
Res[Ask[i].id]=ans;
}
复杂度证明
0.区间排序
建个结构体,用 (sort) 跑一遍即可。平均复杂度 (O(nlog n))。
1.左指针的移动
设每个块 (i) 中分布有 (x_i) 个左端点
由于莫队的添加、删除操作复杂度为 (O(1)),那么处理块 (i) 的最坏时间复杂度是 (O(x_isqrt n)),指针跨越整块的时间复杂度为 (O(sqrt{n})),最坏需要跨越 (n) 次
总复杂度 (O(sum{x_insqrt n}+nsqrt n)=O(nsqrt n))
2.右指针的移动
设每个块 (i) 中分布有 (x_i) 个左端点,由于左端点同块的区间右端点有序,那么对于这 (x_i) 个区间,右端点最坏只需总共 (O(n)) 的时间跳(最坏需跳完整个序列),总共 (nsqrt n)个块,总复杂度 (O(nsqrt n))
至此可得出,莫队算法的总时间复杂度为 (O(nsqrt n)+O(nsqrt n)+O(nlog n)=O(nsqrt n))
与排序之前相比,降低了一个根号之多,有了质的提升
优化方式
目前我只知道奇偶性优化,之后的学习了会再补
由于在当前块所有的询问完成之后,按照朴素的莫队方法,处理下一个块时会将右指针移回来
略过了中间的询问,从而造成不必要的浪费
如果在回来的过程中顺便处理掉他们,时间复杂度就会优秀不少
具体就是把块按奇偶分,奇数块从左到右扫,偶数块从右到左扫
inline int Cmp(node x,node y){return x.l/sqrtn==y.l/sqrtn?((x.l/sqrtn)&1)?x.r<y.r:x.r>y.r:x.l/sqrtn<y.l/sqrtn;}
另外,还可以用规定块长的方法优化,但是并不稳定
例题
关于莫队还有树上莫队,带修莫队什么的
还没学,学了之后会补