莫队学习笔记
莫队是针对区间操作问题的一种算法,利用一种近乎QJ(骗分神技)测试点的剪枝方法,强行使程序的时间复杂度降一个根号。
算法基础:
1、分块:分块是莫队的来源,利用分块思想,可以将询问分块,达到缩短查询区间,降低时间复杂度的目的。
2、结构体关键字排序:莫队的精髓所在,通过对询问区间排序,可以减少指针的移动总距离,使总复杂度较为优秀。
3、单调指针的简单应用:莫队的重要工具,通过两个指针的扫描,可以由前一区间转移到该区间,统计答案。
4、离散化:数据范围极大时能起到巨大的优化作用,缩小总区间,使指针的移动更加快捷高效。
5、卡常技巧:哎嘿嘿~~(其实莫队本质上是个暴力QwQ)。
算法实现:
首先我们来看一个例题:
题目描述
输入格式
输出格式
样例
数据范围与提示
观察题面,我们发现,这不过是一道区间操作问题。
首先考虑暴力思路,对于每一个询问,直接扫描对应区间即可。但是$O(nm)$的复杂度证明这肯定不是正解qwq。
那我们考虑优化,因为该题不需要修改,所以任意区间的答案相当于是固定的。
那我们可不可以用我们已知答案的区间,来更新出我们询问的区间呢?
答案是可以的,这也就是莫队算法的由来。
考虑如何更新新的区间,可以想到用两个树状数组维护每个区间中在[l,r]中的数值,及不同的权值数量,每次将指针移动,更新状态即可。
#include<bits/stdc++.h> #define re register #define lowbit(x) ((x)&(-(x))) using namespace std; int n,m,bel[1000005],size,tot,data[1000005]; int tr1[4000005],tr2[4000005],cnt[1000005]; int la[1000005],ra[1000005]; struct node{int l,r,a,b,id,la,ra;}q[1000005]; inline bool cmp(node a,node b) {return (bel[a.l]^bel[b.l])?bel[a.l]<bel[b.l]:((bel[a.l]&1)?a.r<b.r:a.r>b.r);} inline int read(){ re int a=0,b=1;re char ch=getchar(); while(ch<'0'||ch>'9') b=(ch=='-')?-1:1,ch=getchar(); while(ch>='0'&&ch<='9') a=(a<<3)+(a<<1)+(ch^48),ch=getchar(); return a*b; } inline void add1(re int x,re int y){ for(;x<=m;x+=lowbit(x)) tr1[x]+=y; } inline int getsum1(re int x){ re int res=0; for(;x;x-=lowbit(x)) res+=tr1[x]; return res; } inline void add2(re int x,re int y){ for(;x<=m;x+=lowbit(x)) tr2[x]+=y; } inline int getsum2(re int x){ re int res=0; for(;x;x-=lowbit(x)) res+=tr2[x]; return res; } inline void inc(re int x){ if(++cnt[x]==1)add2(x,1);add1(x,1); } inline void del(re int x){ if(--cnt[x]==0)add2(x,-1);add1(x,-1); } signed main(){ n=read(),m=read(); size=sqrt(n); tot=(n+size-1)/size; for(re int i=1;i<=tot;i++) for(re int j=(i-1)*size+1;j<=i*size;j++) bel[j]=i; for(re int i=1;i<=n;i++) data[i]=read(); for(re int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].a=read(),q[i].b=read(),q[i].id=i; sort(q+1,q+m+1,cmp); re int l=q[1].l,r=q[1].r; for(re int i=l;i<=r;i++) inc(data[i]); la[q[1].id]=getsum1(q[1].b)-getsum1(q[1].a-1); ra[q[1].id]=getsum2(q[1].b)-getsum2(q[1].a-1); for(re int i=2;i<=m;i++){ while(l<q[i].l)del(data[l++]); while(l>q[i].l)inc(data[--l]); while(r<q[i].r)inc(data[++r]); while(r>q[i].r)del(data[r--]); la[q[i].id]=getsum1(q[i].b)-getsum1(q[i].a-1); ra[q[i].id]=getsum2(q[i].b)-getsum2(q[i].a-1); } for(re int i=1;i<=m;i++) printf("%d %d\n",la[i],ra[i]); return 0; }
至此,我们可以把莫队算法理解为一种优雅的暴力,只不过它的剪枝极为巧妙,达到了理想的效果。
以上仅为静态莫队,相应的还有带修莫队、树上莫队、回滚莫队、二维莫队等,在这里先咕掉了。。。(溜...