优雅的区间暴力莫队算法
莫队算法是优雅的暴力而不是什么神奇的数据结构。
只要是区间离线可以算的莫队几乎都可以达到O(n sqrt (n) )的时间复杂度
为什么叫莫队算法呢?据说这是2010年国家集训队的莫涛在作业里提到了这个方法。
由于莫涛经常打比赛做队长,大家都叫他莫队,该算法也被称为莫队算法。
一个例题:给出数组n个元素a[]和Q组询问[L,R]求[L,R]中那些数字出现了T次?
Subtask1:对于99%的数据 1<=L<R<=1000 ,1<=n<=1000,1<=Q<=100
Subtask1.5:对于99.999%的数据 1<=L<R<=100000 ,1<=n<=100000,1<=Q<=10000且数据保证随机
Subtask2:对于100%的数据 1<=L<R<=100000 ,1<=n<=100000,1<=Q<=10000且数据保证一定程度上的随机
Subtask1怎么做?
暴力?怎么暴力:
- 我们用一个cnt[ ]数组记录每种元素,用桶排的思想,枚举区间,
- 每遇到一个元素对应的桶++,然后暴力一遍所有的桶,等于1的我们ans就++,
- 这样统计不同的个数,看看是不是等于L到R,
- 然后再清空桶和ans,做下一组询问。
好吧这样你就学会了99%的莫队算法。
Subtask1.5 优化暴力
显然处理询问的次序和时间复杂度有有关系,如果确定合理的次序这样也不难成为一个好方法。。
剩下1%就是怎么确定搞询问的次序,一种可行的方法是让L和R恰好单增,让前面可以用的东西尽可能多
但是这样的表现不好。特别是面对精心设计的数据,这样方法(按照L排序R排序)表现得很差。
/* 举个栗子,有6个询问如下:(1, 100), (2, 2), (3, 99), (4, 4), (5, 102), (6, 7)。 这个数据已经按照左端点排序了。用上述方法处理时,左端点会移动6次,右端点会移动移动98+97+95+98+95=483次。 其实我们稍微改变一下询问处理的顺序就能做得更好:(2, 2), (4, 4), (6, 7), (5, 102), (3, 99), (1, 100)。 左端点移动次数为2+2+1+2+2=9次,比原来稍多。右端点移动次数为2+3+95+3+1=104,右端点的移动次数大大降低了。 */
首先:考虑我们有两个指针。一个叫做curL,另一个叫curR。他们对应的是所指的数的标号。这样我们可以利用这两个指针进行移动,每次只能向左或向右移动一步。移动的复杂度是O(1)。
我们现在处理了curL—curR区间内的数据,现在左右移动,比如curL到curL-1,只需要更新上一个新的3,即curL-1。
那么curL到curL+1,我们只需要去除掉当前curL的值。因为curL+1是已经维护好了的。
curR同理,但是要注意方向哦!curR到curR+1是更新,curR到cur-1是去除。
我们先计算一个区间 [curL curR] 的answer,这样的话,我们就可以用O(1)转移到 [curL-1 curR] [curL+1 curR] [curL curR+1] [curL curR-1] 上来并且求出这些区间的answer。
我们利用curL和curR,就可以移动到我们所需要求的[L R]上啦~
Subtask2: 怎么优化1.5?——分块
我们把所有的元素分成多个块(即分块)。分了块跑的会更快。再按照右端点从小到大,左端点块编号相同按右端点从小到大。
程序实现:
# include <bits/stdc++.h> using namespace std; const int MAXN=100005; struct rec{ int p,bl,l,r; }q[MAXN]; int n,m,bo[MAXN],a[MAXN],answer; int ans[MAXN]; bool cmp(rec a,rec b) { return (a.bl<b.bl||(a.bl==b.bl&&a.r<b.r)); } void add(int pos){ //将a[pos]加入并更新answer } void del(int pos){ //将a[pos]去除并更新answer } int main() { scanf("%d%d",&n,&m); int block=sqrt(n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); for (int i=1;i<=m;i++) { scanf("%d%d",&q[i].l,&q[i].r); q[i].p=i; q[i].bl=(q[i].l-1)/block+1; } sort(q+1,q+1+m,cmp); int curL=0,curR=0; for (int i=1;i<=m;i++) { int L=q[i].l,R=q[i].r; while (curL>L) add(--curL); while (curL<L) del(curL++); while (curR>R) del(curR--); while (curR<R) add(++curR); ans[q[i].p]=answer; } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }