【算法笔记】离线处理2——初探莫队
【例题Part.1】
洛谷 P4137 Rmq Problem / mex
https://www.luogu.com.cn/problem/P4137
经典mex问题,也可以离线搞
开一颗权值线段树,每一个叶子节点记录它对应的权值最后一次出现的位置(如果没有出现过则记为0),线段树维护区间最小值。
查询按照右端点升序排序,回答右端点为r的查询时,权值线段树里面记录的值是[1,r]区间内每个数最后一次出现的位置
回答查询[l,r]时在线段树里面二分答案区间(如果左子区间的值<l,就进入左子树,否则进入右子树)
这个方法和上一篇 离线处理1 的方法差不多,算是做一个回顾吧。顺便解锁了一个权值线段树的新用法。
当然,你也可以把这个权值线段树改成 可持久化的,就可以在线解决查询了。
个人认为可持久化权值线段树不等于主席树,虽然“主席树”这个概念不是很清楚,但我对它的定义是“在可持久化权值线段树的时间轴上做前缀和的一种方法”
既然讲查询的离线处理,怎能不讲莫队呢?
莫队是一种对多次查询进行离线处理的方法,主要思路就是从一个查询“爬”到另一个查询。
以区间查询为例,对于两个查询q1=[l1,lr] q2=[l2,r2],已知ans(q1),我们每一次做如下操作其中的一个
l1++
l1--
r1++
r1--
进行若干次操作后,l1=l2 r1=r2
每一次操作的同时,都维护有关信息,保证在爬到l1=l2 r1=r2的位置后,可以在短时间内查询ans(q2)
每一次都要一步一步地爬,难难道不会TLE吗?
我们可以把所有的查询按照一个特定的顺序排序,然后依次解决。
具体而言就是把长度为n的序列分块,每块长度大约是sqrt(n)
把所有查询按照左端点所在块为第一关键字(升序),右端点的位置为第二关键字(升序)排序,查询时就从上一个查询爬到下一个就好了。
为什么这样排序可以保证时间复杂度呢?
让我们胡乱分析一下:对于左端点在同一个块的查询,右端点滑动O(n)次就可以解决左端点在一个块内的查询,一共有O(sqrt(n))个块,所以右端点滑动 O(n sqrt(n))次。
再看左端点,最坏情况下,假设每一次都要从一个块的左端滑到右端,那么每一次查询就是O(sqrt(n)) 次滑动,一共n次查询(假设查询次数m和n同阶)
左端点的滑动次数也是O(n sqrt(n))的
综上所述,假设每次滑动的时间复杂度都是O(1)的,那么滑动的总时间复杂度就是O(n sqrt(n))的,限时1000ms的话,可以通过n<=5e5的数据
莫队其实很简单,只要可以找到O(1)的滑动,O(sqrt(n))以内的查询,就可以直接把模板改一下,轻松AC。
【例题】
https://www.luogu.com.cn/problem/P4462
上来就是紫题?
注意:k是固定的
如果你熟悉异或运算的特性,你就可以想到做一个异或前缀和(sum[i]=a[1]^a[2]^a[3]...a[n])
然后每一次查询q=[l,r]的实质就是 计数(a,b):sum[b]^sum[a]=k a,b∈[r,l-1],a<b
那么怎么爬呢?
用bkt[i]表示 计数k: k∈当前区间 bkt[k]=i
同时存下来当前区间的答案 ans
每次加入一个新的数a,ans+=bkt[k^a],然后更新bkt
删除同理,就是加入操作的逆操作
由于我们直接维护了答案的值,查询只需要O(1)的时间
下面的题目就会出现非直接维护答案的情况,也就是滑动到目标位置后,还要再做一个O(反正不是1)的查询
https://www.luogu.com.cn/problem/P3674
熟悉这位出题人的同学,应该会明白这又是一道毒瘤题
但这道题是很有意义的,它告诉我们std::bitset这个东西是多么的神(wei)奇(suo)
bitset单点修改O(1)的(很好的契合了莫队的特性),求count,位运算都是O(n/w)的,严格来说w是常数,时间复杂度还是O(n),然而这个w的大小相当可观,就是你的计算机的字长(就是64位,32位或16位)
现在计算机基本上都是64位的了,在开O2优化的情况下,可以很快地做位运算和求count
莫队里面用一个bitset维护区间内的每一位是否出现过,同时在维护一个倒序的bitset
求和为k的数对的个数,就是把一个bitset和它的逆序bitset位移一下做一个按位与 O(n/64)
求差为k的数对的个数,就是把一个bitset和它自己位移一下做一个按位与 O(n/64)
求积为k的数对的个数,就直接枚举约数,时间复杂度 O(sqrt(n))
卧槽,还有这种操作?
下一题可以值域分块来维护莫队,O(1)滑动,O(sqrt(n))查询
如果你想不到的话,不要紧,你已经学会了bitset+莫队 的赖皮算法了,开个O2就AC了
虽然可以bitset乱搞,但还是要介绍一下值域分块的做法,个人认为O(1)单点修改 O(sqrt(n))查询的分块和莫队的需求简直是完美契合

浙公网安备 33010602011771号