莫队算法
本质上。。似乎是大暴力。。。
##传说中能解决一切区间问题的算法
如果我们**知道区间
[
L
,
R
]
[L,R]
[L,R],就能在比较短的时间内求出
[
L
−
1
,
R
]
,
[
L
+
1
,
R
]
,
[
L
,
R
−
1
]
,
[
L
,
R
+
1
]
[L-1,R],[L+1,R],[L,R-1],[L,R+1]
[L−1,R],[L+1,R],[L,R−1],[L,R+1]**的话,那就可以用莫队算法了。
有一种经典的问题:给你一些不带修改的区间询问,要求快速回答
显然,有一些我们可以通过线段树来完成,因为线段树是 O ( N l o g N ) O(NlogN) O(NlogN)的
但是,线段树有的东西是维护不了的。
看一个例子
给你一个数列,若干询问,要求回答区间内同种颜色的数量。
线段树很难做,怎么办?
用莫队算法!
莫队算法的实质是通过将询问排序,每个询问均由前一个询问(排序后的)转移得来,通过一定的排序优化时间复杂度。往往可以有 O ( N N ) O(N\sqrt N) O(NN)的效果
回到题目
显然对于两次询问
L
,
R
L,R
L,R和
L
′
,
R
′
L',R'
L′,R′,知道了
L
,
R
L,R
L,R的答案,就可以暴力计算
∣
L
−
L
′
∣
+
∣
R
−
R
′
∣
|L-L'|+|R-R'|
∣L−L′∣+∣R−R′∣次得出
L
′
,
R
′
L',R'
L′,R′的答案。
∣ L − L ′ ∣ + ∣ R − R ′ ∣ |L-L'|+|R-R'| ∣L−L′∣+∣R−R′∣。也就是曼哈顿距离
把每个询问看作是二维平面上的点,那么我们的最小总时间,就是这些点的最小曼哈顿距离生成树, 按照这个树的顺序做,复杂度变成了 O ( N N ) O(N\sqrt N) O(NN)(为什么?),而且这个生成树连边也有特别的技巧,可以去看莫队在知乎上推荐的那篇。https://zhuanlan.zhihu.com/p/25017840
然而这样有一点猥琐
有一个优美方便简洁好理解的替代品
分块大法好!
把整个序列分块,把 L L L按照所在块的顺序为第一关键字,把 R R R( R R R本身!)为第二关键字排序。
为什么要分块不能直接排呢?
分块很好的减少了一种情况的影响。
L < L ′ < L ′ ′ L<L'<L'' L<L′<L′′,并且距离很近,但是 R ′ < R < R ′ ′ R'<R<R'' R′<R<R′′,并且 R ′ R' R′与另外两个离得很远。如果直接按 L L L排序就会浪费非常多的时间。
由于每个块的大小是 N \sqrt N N
分块使得两个询问之间差异平均到了 L , R L,R L,R上。
因此,理论复杂度大约是 O ( N N ) O(N\sqrt N) O(NN),实际上是 O ( O( O(玄学??跑的过就行 ) ) )(假设询问数与序列长度同阶)
带上了修改,怎么办?
然后我们继续分块。
把二元组 L , R L,R L,R变成三元组 L , R , x L,R,x L,R,x。 x x x是这次询问在修改第几次后。
然后把 R R R所在块看作第二关键字, x x x看作第三关键字排序即可。
转移时直接恢复(或删除)两次询问之间的修改。如果在区间内还要计算对答案的影响
然而修改的复杂度不同
最优分块方式是每块
N
2
3
N^{2\over 3}
N32,总共
N
1
3
N^{1\over 3}
N31块
左指针移动
N
∗
N
2
3
=
N
5
3
N*N^{2\over 3}=N^{5 \over 3}
N∗N32=N35次
右指针移动
N
∗
N
2
3
=
N
5
3
N*N^{2\over 3}=N^{5 \over 3}
N∗N32=N35次
修改指针移动
(
N
1
3
)
2
∗
N
=
N
5
3
{(N^{1\over 3})}^2*N=N^{5 \over 3}
(N31)2∗N=N35次
总复杂度 N 5 3 N^{5 \over 3} N35
在实际操作中,有时取一些什么 n ∗ 10 \sqrt n*10 n∗10之类的奇葩的数可能更快
有一道板题
http://blog.csdn.net/hzj1054689699/article/details/51880644
回滚莫队(仅插入无需删除)
有时候我们需要处理一些特殊情况,例如维护区间最大最小值。
加入的时候我们很好处理,但删除就不知道该怎么办了。
此时就需要一些处理技巧。
我们仍然对于询问排序,将序列分块,第一关键字为左端点所在块的位置,第二关键字为右端点位置。
对于询问左端点在同一块的情况,右端点是有序的,但左端点可能在这块中是无序的,那么我们直接将左端点定在这一块的右端,由于询问右端点是单调递增的,右端点可以不断向后扩展,碰到一个询问,我们就将当前左端点暴力移到询问的左端点,计算答案,再暴力移回当前块的右端。
可能有疑惑:这不是一样需要删除吗?
其实不然,我们可以在左移前保存状态,计算完直接恢复。或者在每个位置记录在哪里修改了,原状态是什么,回滚的时候将修改撤销即可。
与普通莫队相比,每一个询问我们仅仅是左端点多移了不超过一个块的距离,因此总复杂度仍然是 m n m\sqrt n mn的,非常优秀!