
%一波莫涛队长orz
由于没有什么板子,这里放一道基础题。
给定一个序列,其中有n个元素,有m次查询,每次查询要求输出区间[l,r]内出现次数不少于k的数字的个数。
其实乍一看,好像就是可以用暴力做……但其实莫队本身也和暴力区别不大,基础思路就是将序列分成sqrt(n)个块,然后在每个块中进行操作。
以下是代码
1 |
|
在这个程序中,我们用num[j]来记录j的出现次数,用cnt[j]记录出现次数不小于j的数的个数,在离线操作的基础上,对每次查询依照左端点所在的块进行排序,如果所在块相同则按照右端点进行排序,可以保证L,R两个指针的跳动次数尽可能的小以降低复杂度。
然后在对每次查询的更新中不断跳动L、R,拓宽区间的时候先拓宽再操作,缩小区间的时候则先操作再缩小,最终在记录下来的ans数组中查询答案。
以下是几道题:
手写AC(调了一下午的绝望)
1 |
|
这道题说起来难度其实不大,一个值域分块的莫队保证m√n的复杂度完全没压力,就是这个28Mb的空间稍微有点坑。
手写AC(一遍A O2-1296ms)
1 |
|
仍旧是对0到n进行分块,因为最坏情况是a[1]~a[n]对应0~n-1。所以就在上一题的基础上修改一下更新操作,在每次更新的时候与当前的最小值进行比较,如果最小值数量增加就寻找新的最优解,如果有新的解出现并且优于最优解就更新最优解。
手写AC
1 |
#include <bits/stdc++.h> |
几乎无思维难度,在BZOJ 3809的基础上加一个查询数量的函数就行了
带修莫队
手写AC
1 |
|
就难度系数而言其实并不算高,作为带修莫队的板子题来说挺不错的。
那么以下是带修莫队的思路。
如果我们把每次修改当作一次时间的推移,然后就只需要在查询中加入一个新的关键字Tim,也就是时间,仅在两个询问的l和r都在同一分块中的情况才依照Tim进行排序。
然后在操作中进行L和R的跳动之前先进行时间指针T的跳动,使时间到达当前查询的时间点。由于时间需要不停的跳动,所以对于每次修改操作要另开一个结构体记录修改位置(pos)、修改后的值(New)和修改前的值(Old),这个Old在后面时间倒流的时候会有用。
对于每次T的跳动进行一次Change操作,时间增加就将指定位置的值修改为T++后的修改操作的New值,时间倒流就先将指定位置的值修改为T时修改操作的Old值(即还原),然后就没什么区别了。
对于Change操作也比较简单,如果当前位置在[L,R]区间中就将修改后的值Add进去,然后将原位置上的值Minus掉,如果不在区间内就直接将原位置数据赋为修改后的值,因为以后指针跳动的时候会自行更新的。
稍微扯两句,带修莫队的复杂度经过某些dalao的证明后确定为n^(5/3),此时分为n^(1/3)块,每块有n^(2/3)个元素,所以blo=pow(n,2.0/3)。
树上莫队
例题:BZOJ 4129: Haruna’s Breakfast(小天使!!!!!!!!)
什么,树上能跑莫队,还带修改?那岂不是要爆裂吗?
这是我第一次听说树上莫队时的想法。
不过我们知道对于一棵树,还有DFS序这种美妙的东西,这样的话树不就变成序列了吗?
比如说对于下面一棵树:
它的DFS序就是:1 2 4 4 5 5 2 3 6 6 7 7 3 1
然后求j,k两点之间的链上的mex值。我们用in[i]记录i第一次出现的位置,用out[i]记录i最后出现的位置。
首先假设j是k的祖先,那么我们可以发现[j,k]间的信息就存在in[j],in[k]之间或out[j],out[k]之间,对于出现两次的数我们就忽略掉好了。
那么如果j不是k的祖先呢?我们会发现[j,k]之间的信息存在了in[j],out[k]之间或者out[j],in[k]之间,同样我们忽略出现了两次的数。但是如果仔细看一下就会发现,j、k的LCA好像不见了,那么我们只要特殊处理一下LCA就OK了。
然后就可以用序列上的莫队来处理了DA☆ZE!(不过代码比较长就是了)
以下是AC代码(我可是自带大常数和大空间的男人)
1 |
|
这里LCA我用的树剖(当然倍增也可以啦
顺便注明一下,那个tag数组是用来记录某个位置在区间里出现了几次,因为不论是0次还是2次都是不计入答案的,所以只要把2次当0次看就可以避免很多复杂的讨论了。然后修改的时候tag是0就加,tag是1就减,最后要把tag进行变换(即0->1,1->0)。
要说修改与mex这两个的话,还是看上面的讲解吧。
再说起来,其实树上莫队可以在树上分块的基础上搞,但是我太蒻了,看不懂dalao的讲解。
还有一题:P4074 [WC2013]糖果公园
几乎就是板子吧,具体自己看代码(其实没啥差别)
手写AC(O2-6s,莫名其妙就Rank 5了)
1 |
|
以上就是莫队——一个优雅的暴力算法




近期评论