因为今天考到莫队裸题了嘤嘤嘤...而我这样的蒟蒻肯定不会这样的高端算法啊QAQ。于是暴力水了40分qwq。
正如上文所说,我实在太菜了,于是学习莫队也只是学习了最简单的不带修普通莫队,如果我能苟到省选那就再继续学啦。
掏心推荐:深度好文,浅谈根号算法--分块 By new2zy
一、莫队的思想及处理的问题
红太阳金涛说的吼啊:莫队本质上就是一种优化的暴力。
所以莫队到底能适用什么样的问题呢?基本上所有的离线区间查询问题(暂时不带修)都能用莫队算法苟过233...所以莫队算法还真是强啊%%。
离线区间查询?意思就是在说,我们先把所有需要进行区间查询的信息保存下来,然后根据一定的方法规则对这些区间查询信息进行排序。(这里我们一会再说qwq)
排序之后,我们就可以用两个指针$posl$和$posr$(可能有些像cf上的two pointer?不太确定qwq),不断调整他们的位置直到与查询区间精确重合并维护$[posl,posr]$区间内的信息,来处理询问。
而莫队算法也是需要把序列分成很多块的(分块算法的好基友),一般是分成$sqrt(n)$块,当然也有其他玄学分块方式。
而莫队的时间复杂度一般是$O(n*sqrt(n))$的,具体分析过程我就光速逃了qwq。(不会qwq)
另外,刚才我们留了个锅,就是关于将询问排序的方法:
- 一般的排序是这样的:先按左端点所在块排,再按右端点位置排。但是这个排序有时比较弱,卡不过毒瘤们的精心构造。
- 于是产生了更加优秀的一种排序:按奇偶块排序。这也是比较通用的。如果区间左端点所在块不同,那么就直接按左端点从小到大排;如果相同,鸡块奇块按右端点从小到大排,偶块按右端点从大到小排。
bool cmp(query a,query b) { return (a.l/block)^(b.l/block) ? a.l<b.l : (((a.l/block)&1) ? a.r<b.r : a.r>b.r); }
至于证明...我太菜了放过我吧
二、丢几道例题跑
其实..莫队的核心代码除排序的cmp外只有四行...
int l=ask[i].l,r=ask[i].r; while(posl<l) remove(posl++); //当前区间左端点在查询区间的左边 想要向右移 但是因为当前的posl不在查询区间中所以把它对答案的贡献去除。 while(posr>r) remove(posr--); //其他同理233 while(posl>l) add(--posl); while(posr<r) add(++posr); ans[ask[i].id]=noww;
什么这不是六行嘛
例题1 小B的询问
莫队裸题。左右指针移动实际上就是数字的增减,我们改变了桶的大小(计数数组大小后),考虑如果一个数字p减小,那么它对答案的贡献就会少。少了多少呢?考虑完全平方公式。设$x=cnt[p]$。
$(x+1)^2$------->>>>>>$x^2$
$x^2+2*x+1$------->>>>>>$x^2$
显然少了$2*x+1$,那么增加同理。之后就是套裸的莫队了。
#include<cstdio> #include<algorithm> #include<cmath> using namespace std; int n,m,k,block,posl=1,posr,noww; int seq[100090],ans[100090],sum[100090]; struct query{ int l,r,id,in; }ask[100090]; bool cmp(query a,query b) { return (a.l/block)^(b.l/block) ? a.l<b.l : (((a.l/block)&1) ? a.r<b.r : a.r>b.r); } void remove(int x) { sum[seq[x]]--; noww-=2*sum[seq[x]]+1; } void add(int x) { sum[seq[x]]++; noww+=2*sum[seq[x]]-1; } int main() { scanf("%d%d%d",&n,&m,&k); block=sqrt(n); for(int i=1;i<=n;i++) scanf("%d",&seq[i]); for(int i=1;i<=m;i++) { scanf("%d%d",&ask[i].l,&ask[i].r); ask[i].id=i; ask[i].in=ask[i].l/block; } sort(ask+1,ask+1+m,cmp); for(int i=1;i<=m;i++) { int l=ask[i].l,r=ask[i].r; while(posl<l) remove(posl++); while(posr>r) remove(posr--); while(posl>l) add(--posl); while(posr<r) add(++posr); ans[ask[i].id]=noww; } for(int i=1;i<=m;i++) printf("%d ",ans[i]); return 0; }
例题2
也是一道裸的不带修莫队。因为每个数范围在1e9内,所以先离散化一下,然后上裸的莫队。但是正睿神机(?)竟然卡unique......嘤。
#include<cstdio> #include<algorithm> #include<cmath> using namespace std; int n,Q,block,m,posl=1,posr,noww; int seq[500090],b[500090],tong[500090],ans[500090]; struct query{ int l,r,id,in; }ask[500090]; void re(int &x) { x=0; char ch=getchar(); bool flag=false; while(ch<'0'||ch>'9') flag|=(ch=='-'),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); x=flag ? -x : x; } bool cmp(query a,query b) { // return (a.l/block)^(b.l/block) ? a.l<b.l : (((a.l/block)&1) ? a.r<b.r : a.r>b.r); return a.in==b.in?(a.in&1)?a.r<b.r:a.r>b.r:a.in<b.in; } void remove(int x) { tong[seq[x]]--; if(tong[seq[x]]==2) noww++; if(tong[seq[x]]==1) noww--; } void add(int x) { tong[seq[x]]++; if(tong[seq[x]]==2) noww++; if(tong[seq[x]]==3) noww--; } int main() { re(n);re(Q); for(int i=1;i<=n;i++) re(seq[i]),b[i]=seq[i]; sort(b+1,b+1+n); // m=unique(b+1,b+1+n)-(b+1); for(int i=1;i<=m;i++) seq[i]=lower_bound(b+1,b+1+m,seq[i])-b; block=sqrt(n); for(int i=1;i<=Q;i++) { re(ask[i].l);re(ask[i].r); ask[i].id=i;ask[i].in=ask[i].l/block; } sort(ask+1,ask+1+Q,cmp); for(int i=1;i<=Q;i++) { int l=ask[i].l,r=ask[i].r; while(posl<l) remove(posl++); while(posr>r) remove(posr--); while(posl>l) add(--posl); while(posr<r) add(++posr); ans[ask[i].id]=noww; } for(int i=1;i<=Q;i++) printf("%d ",ans[i]); return 0; }
更强的莫队算法,以后再见啦:)(如果我联赛后还能继续苟233)