莫队算法
时间复杂度:不修改的时间复杂度 为n*logn 修改的n*pow(n,0.6666)
思想: 优化的暴力,对询问进行离线化的操作,减少一些重复的元素的时间复杂度。
应用:
- 针对某些以元素大小为单位的大量询问,给出了,l 和 r。
- 其实看到 l,r 就可以想想是不是线段树,莫队,树状数组啥的。
一般莫队:
#include <bits/stdc++.h> using namespace std; #define ri register int #define M 100055 template <class G > void read(G &x) { x=0;int f=0;char ch=getchar(); while(ch<'0'||ch>'9'){f|=ch=='-';ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x=f?-x:x; return ; } struct dian{ int l,r,pos,id; bool operator <(const dian &t)const { if(pos==t.pos&&r==t.r) return l<t.l; if(pos==t.pos) return r<t.r; return pos<t.pos; } }p[M]; int n,m; int val[M],num[M]; int ANS[M]; int ans=0; void add(int a) { a=val[a];num[a]++; if(num[a]==1) ans++; } void del(int a) { a=val[a];num[a]--; if(num[a]==0) ans--; } int main(){ read(n);read(m); for(ri i=1;i<=n;i++) { read(val[i]); } int d=sqrt(n)+0.5; for(ri i=1;i<=m;i++) { read(p[i].l);read(p[i].r); p[i].id=i; p[i].pos=p[i].l/d; } sort(p+1,p+1+m); int l=1,r=1; add(r); //初始化 for(ri i=1;i<=m;i++) { while(l<p[i].l) del(l++); while(r>p[i].r) del(r--); while(l>p[i].l) add(--l); while(r<p[i].r) add(++r); ANS[p[i].id]=ans; } // 写的时候收缩和扩张,分开写,检查的时候,利用while看是++还是--,在前在后,看扩张和收缩。 for(ri i=1;i<=m;i++) { printf("%d\n",ANS[i]); } return 0; }
修改莫队:
struct dian{ int l,r,pos,id,tim; bool operator <(const dian &t)const { if(pos==t.pos) { if(r/d==t.r/d) return tim<t.tim; return r<t.r; } return l<t.l; } }p[M]; void work (int v,int k) { vis[cent[v]]--; cent[v]+=k; vis[cent[v]]++; } void work_tim(int ti,int i) { if(p[i].l<=gai[ti].mubiao&&gai[ti].mubiao<=p[i].r) { work(val[gai[ti].mubiao],-1); work(gai[ti].VAL,1); } swap(val[gai[ti].mubiao],gai[ti].VAL); } d=pow(n,0.6666); for(ri i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); if(a==1) { p[++trmp].l=b; p[trmp].r=c; p[trmp].id=trmp;p[trmp].tim=sj; p[trmp].pos=p[trmp].l/d; } else { gai[++sj].mubiao=b; tt[n+sj]=gai[sj].VAL=c; } } sort(p+1,p+trmp+1); sj=0; for(ri i=p[1].l;i<=p[1].r;i++) { work(val[i],1); } int baba; while(sj<p[1].tim) work_tim(++sj,1); while(sj>p[1].tim) work_tim(sj--,1); // li hai for (ANS[p[1].id]=1;vis[ANS[p[1].id]]>0;ANS[p[1].id]++); for(ri i=2;i<=m;i++) { int l=p[i-1].l; int r=p[i-1].r; while(l<p[i].l) work(val[l++],-1); while(r>p[i].r) work(val[r--],-1); while(l>p[i].l) work(val[--l],1); while(r<p[i].r) work(val[++r],1); while(sj<p[i].tim) work_tim(++sj,i); while(sj>p[i].tim) work_tim(sj--,i); //跟新ans }
注意 d的取值为什么有时取 pow(n,0.6666)呢 而不是 int(0.5+sqrt(n))
后记:
- 写主要代码的时候 收缩和扩张分开写,检查的时候,利用while看是++还是--,在前在后,看扩张和收缩。
- 初始化 l=r=1,add(r),就行啦