因为学校的小朋友不知道莫队是什么……所以就写了这个
普及向……没有什么东西……会的话就不用看了……
因为是在学校用记事本写的……所以求和符号没有用latex……手画2333
对于一些特殊的数据结构题……比如询问序列中区间[l,r]的什么东西,
如果我们已经知道了[l,r]的答案,那么就能在O(1)或O(lgn)的时间得到[l,r+1]或[l-1,r]的答案
而且允许离线
要是题目满足这个性质,我们就可以用莫队在O(n^1.5)或O(n^1.5lgn)的时间内得到所有询问的答案
怎么做?
拿《小Z的袜子》(莫涛)来说好了
description
给定一个数列,m个询问
每次询问:
区间中选两个数,两个数相等的概率
若概率为0则输出0/1
SOL
就是说 我们要对一个区间求
all values
===
\
- C(2,x) (x表示值i在区间[l,r]出现的次数)
/
===
i=1
---------------(这是分数线……)
C(2,r-l+1)
分母显然随便求= =
所以我们来看分子= =
分子是组合数,看起来就不好求……所以改变形式
C(2,x)=(x^2-x)/2
所以变成了求
=== ===
\ \
- x^2- - x (x表示值i在区间[l,r]出现的次数)
/ /
=== ===
---------------(这是分数线……)
2C(2,r-l+1)
显然
===
\
- x (x表示值i在区间[l,r]出现的次数)
/
===
这个值就是r-l+1
那么我们的问题就是如何快速求出
===
\
- x^2 (x表示值i在区间[l,r]出现的次数)
/
===
这个正常的做法不好做
因为这个没有区间加法= =也没有区间减法
对序列分块无效
我们考虑如果我们已经知道了区间[l,r]每个数出现的次数……
用一个数组 A[]存下来,然后A[i]表示i在区间[l,r]出现的次数
同时当前的
===
\
- x^2 (x表示值i在区间[l,r]出现的次数)
/
===
也用一个变量sum表示
那么我们如果想知道[l,r+1]的答案
假设数列第r+1个元素的值为Z
那么这个转移导致sum产生的变化可以这样描述 :sum=sum-(A[Z])^2+(A[Z]+1)^2
对吧= =
因为除了Z以外的数,在[l,r]和[l,r+1]出现的次数都是一样的
变化的只有Z,所以我们减去A[Z]^2,加上(A[z]+1)^2
那么就求出了[l,r+1]的sum值
然后A[Z]++
就得到了区间[l,r+1]的A[]
这样我们就可以O(1)由[l,r]得到[l,r+1]的答案了
由[l,r]得到[l-1,r],[l,r-1],[l+1,r]的方法是类似的……都可以O(1)完成
那么假设我们现在回答了一个询问区间[l,r]的询问,下一个询问为[x,y]
那么我们只需要O(|x-l|+|y-r|)的时间得到下一个询问的答案= =
这样是一个很好的性质,在随机数据表现非常好= =
但是我们显然可以构造出卡直接由一个询问转移到另一个询问的方法
比如我们一开始询问[1,n],然后询问[1,1],然后询问[1,n],然后询问[1,1]……这样每次转移就是O(n)的了……
是不是说这个方法就只适用数据随机的情况呢?
当然不是……
我们把询问[l,r]看成平面的点(l,r)
两个点(x,y)和(l,r)的距离定义为曼哈顿距离|x-l|+|y-r|
那么我们的任务就是在最短的长度内遍历平面内的所有点……
这样就等价于求一个曼哈顿最小生成树= =不过曼哈顿生成树太麻烦了,所以请忽略这一行
[我们接下来把n,m视为同阶……不然讨论复杂度会把我弄得有点晕= =]
我们其实不需要保证最短,只需要保证我们选择的一个遍历方案有一个确定的、可接受的上界就可以了
比如我们刚才的直接转移方法最坏就是O(n^2)的
以下方法可以保证得到的路线为O(n^1.5)级别的长度
先对所有询问按左端点排序
取x=n^0.5
那么所有左端点在[1,x]这个区间的询问并为一个大询问块
左端点在[x+1,2*x]的区间的询问并为一个大询问块
左端点在[2*x+1,3*x]的询问并为..
....
左端点在[(x-1)*x+1,x*x]的询问并为一个询问块
然后对于一个询问块,我们无视左端点,将它们按右端点从小到大排序
然后我们从第一块的第一个询问开始,回答到第一块的最后一个询问,然后再由这个询问的答案转移到第二块的第一个询问,再依次回答第二块的所有询问,再依次回答第三块的所有询问,以此下去即可
这样可以保证我们转移次数为O(n^1.5)也即,
我们得到了总时间复杂度为O(n^1.5)的回答这题的做法,不需要任何高端数据结构,只要数组即可
下面开始分析时间复杂度:
假设我们维护的区间为[x,y]……那么复杂度就是它的左右端点移动次数……
然后我们发现左右端点移动可以分开讨论……
我们先考虑右端点移动次数
首先我们考虑一个块里面的转移情况
由于一个块里面的询问都按右端点排序
所以我们右端点在一个块里面最多移动n次
有 n^0.5个块,那么同一个块内的右端点移动最多就是O(n*(n^0.5))也即O(n^1.5)次
然后考虑从一个块到另一个块导致的右端点变化
最坏情况,右端点由n到1,那么移动n次
有 n^0.5个块
那么从一个块到另一个块的事件只会发生O(n^0.5)次……
所以这种右端点移动的次数也是O(n*(n^0.5))即O(n^1.5)次
没有别的事件导致右端点移动了
综上:右端点移动次数是O(n^1.5)+O(n^1.5)级别的
即O(n^1.5)级别
然后考虑左端点移动次数
同一个块里面,由于左端点都在一个长度为n^0.5的区间里面
所以在同一块里面移动一次,左端点最多变化n^0.5
总共有n个询问……
所以同一块里面的移动最多n次
那么同一个快里面的左端点变化最多是O(n^1.5)的
考虑跨越块
每由第i个块到第i+1个块,左端点最坏加上O(n^0.5)
总共能加上O(n^0.5)次
所以跨越块导致的左端点移动是O(n)的
综上,左端点移动次数是O(n^1.5)+O(n)级别的
也即O(1.5)级别
所以总的移动次数就是左端点移动次数+右端点移动次数
就是O(n^1.5)
每次转移是O(1)的
我们转移O(n^1.5)就得到了所有询问的答案= =
所以时间复杂度是O(n^1.5)
那么就这样了……
考虑扩展……
1.区间众数?
同样,通过维护每个数的出现次数,就可以快速由一个区间得到另一个区间的数的出现个数
我们是在询问区间每个数出现次数的最大值
用线段树或平衡树维护
那么这样转移就是lgn的了
时间 (N^1.5) * lgn
2.树上推广?
树上的小Z的袜子:
树上选择两个点,问在这两个点的路径上的所有数组成的数列中,选两个数,两个数相等的概率
若概率为0则输出0/1
考虑dfs序:
有以下定理 :
两个点在dfs序中的距离不会小于它们在树上的真实距离
所以我们就把询问转化为了区间询问……
不过转移会麻烦一点……?
自行思考
3.COT2?
询问树上两个点的路径上有多少种数
主席提供了一个不错的做法,但是莫队也是可以做的
4.面试的考验?
询问区间两个数绝对值差的最小值
这个我们使用平衡树,同样可以快速由一个区间得到另一个区间的答案
时间O((n^1.5)lgn)
(不过这题在数据随机的时候有更好的做法)
5.带修改?
多想想
6.强制在线?
多想想
为什么我们不选择使用曼哈顿生成树……
~ 其实曼哈顿生成树没有那么难写啦…… ~
为什么更推荐这种分块做法呢?
你可以想想 :笛卡尔树+LCA优化rmq也不难写,但在竞赛中遇到rmq大家写的还是st表居多……
因为分块太好写了= =
而且没记错的话,曼哈顿最小生成树的大小的上界是O(n^1.5)……
和分块的上界一样= =
所以最坏情况的复杂度是一样的……
所以推荐这种对询问分块的方式= =
下次把面试的考验那道题比较高贵的做法写个题解好了233333