看到这个题,你不由自主地想到了莫队算法,(不了解莫队算法的看这里:莫队(一):莫队初步基础与思想),但是你看到题目中还有修改操作,莫队GG了!怎么办?这时候就有了莫队的修改版:
带修莫队
前面说过,莫队算法是离线算法,不支持修改,强制在线需要另寻他法。的确,遇到强制在线的题目莫队基本上萎了,但是对于某些允许离线的带修改区间查询来说,莫队还是能用的。做法就是把莫队直接加上一维,变为带修莫队。
这里我们说的加上一维是什么意思呢?就是把修改操作进行编号,即加入了时间维度,编号即为“时间戳”。在进行查询的时候定义当前时间戳为\(t\),对于每一个查询操作,如果当前的时间戳相对太大了,就说明这里包含了一些要查询的时间点之后的修改,就要把多改的改回去,反之则向后修改。只有当当前区间和查询区间完全一致时,查询结束,此时的答案即是这个查询的结果。
//区间完全一致当且仅当\(l,r,t\)三个维度的值完全一样
这就是带修莫队了,是不是很简单,于是我当年就写了个这:
别着急复制,这个只有40分,题面说: 本题可能轻微卡常数
那我得用尽所有优化方式了,小伙伴们可以把这一份40分代码拿去自己优化一下试试。AC代码放在下面了
#include<cmath> #include<cstdio> #include<string> #include<cstring> #include<algorithm> using namespace std; const int MAXN=2*1e6+10; inline int read(){ char c=getchar();int x=0,f=1; while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0',c=getchar();} return x*f; } int N,M; int a[MAXN],where[MAXN]; struct Query { int x,y,pre,id; }Q[MAXN]; int Qnum=0; struct Change { int pos,val; }C[MAXN]; int Cnum=0; int color[MAXN],ans=0,base,out[MAXN]; int comp(const Query &a,const Query &b){ if(a.x!=b.x) return where[a.x]<where[b.x]; if(a.y!=b.y) return where[a.y]<where[b.y]; return a.pre<b.pre; } void Add(int val){ if(++color[val]==1) ans++; } void Delet(int val){ if(--color[val]==0) ans--; } void Work(int now,int i){ if(C[now].pos>=Q[i].x&&C[now].pos<=Q[i].y){ if(--color[a[C[now].pos]]==0) ans--; if(++color[C[now].val]==1) ans++; } swap(C[now].val,a[C[now].pos]); } void MoQueue(){ int l=1,r=0,now=0; for(int i=1;i<=Qnum;i++) { while(l<Q[i].x) Delet(a[l++]); while(l>Q[i].x) Add(a[--l]); while(r<Q[i].y) Add(a[++r]); while(r>Q[i].y) Delet(a[r--]); while(now<Q[i].pre) Work(++now,i); while(now>Q[i].pre) Work(now--,i); out[Q[i].id]=ans; } for(int i=1;i<=Qnum;i++) printf("%d\n",out[i]); } int main(){ N=read();M=read(); base=sqrt(N); for(int i=1;i<=N;i++) a[i]=read(),where[i]=(i-1)/base+1; while(M--){ char opt[5]; scanf("%s",opt); if(opt[0]=='Q'){ Q[++Qnum].x=read(); Q[Qnum].y=read(); Q[Qnum].pre=Cnum; Q[Qnum].id=Qnum; } else if(opt[0]=='R'){ C[++Cnum].pos=read(); C[Cnum].val=read(); } } sort(Q+1,Q+Qnum+1,comp); MoQueue(); return 0; }
上面一份代码为什么凉凉?不光是少了几步优化,还有一点:有大佬证明了这道题分块大小设置为\(n^{\frac{2}{3}}\)时优于大小为\(\sqrt n\)的情况,总复杂度为\(O(n^{\frac{5}{3}})\),优于大小为\(\sqrt n\)时最差\(O(n^2)\)的总复杂度。
所以就有了这样的代码!
#include<cmath> #include<cstdio> #include<string> #include<cstring> #include<iostream> #include<algorithm> #define RE register using namespace std; const int N=1e6+10; const int M=5e4+10; int n,m,cntq,cntc; int l=1,r,tim,now; int be[M],a[M],ans[M]; int cnt[N]; struct query{ int l,r,id,time; }q[M]; struct node{ int pos,col,last; }c[M]; inline int Read(){ int x=0; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x; } inline void Print(int x){ if(x>9) Print(x/10); putchar(x%10+'0'); } inline bool CMP(query a,query b){ if(be[a.l]^be[b.l]) return be[a.l]<be[b.l]; else if(be[a.r]^be[b.r]) return be[a.r]<be[b.r]; else return a.time<b.time; } //Ask和Change两部分看起来好像没必要拎出来 inline void Ask(int l,int r){ cntq++; q[cntq].l=l; q[cntq].r=r; q[cntq].time=cntc; q[cntq].id=cntq; } inline void Change(int p,int co){ cntc++; c[cntc].pos=p; c[cntc].col=co; } int main(){ n=Read(); m=Read(); int size=pow(n,2.0/3.0); int bnum=ceil((double)n/size); for(RE int i=1;i<=bnum;i++) for(RE int j=(i-1)*size+1;j<=i*size;j++) be[j]=i; for(int i=1;i<=n;i++) a[i]=Read(); for(RE int i=1;i<=m;i++){ char tmp; scanf("\n%c",&tmp); if(tmp=='Q'){ RE int l=Read(),r=Read(); Ask(l,r); } if(tmp=='R'){ RE int p=Read(),co=Read(); Change(p,co); } } sort(q+1,q+cntq+1,CMP); for(RE int i=1;i<=cntq;i++){ int ql=q[i].l,qr=q[i].r,qt=q[i].time; while(l<ql) now-=!--cnt[a[l++]]; while(l>ql) now+=!cnt[a[--l]]++; while(r<qr) now+=!cnt[a[++r]]++; while(r>qr) now-=!--cnt[a[r--]]; //以下就是带修莫队的时间点左右横跳实现部分 while(tim<qt){ tim++; if(ql<=c[tim].pos&&c[tim].pos<=qr) now-=!--cnt[a[c[tim].pos]]-!cnt[c[tim].col]++; swap(a[c[tim].pos],c[tim].col);//记录上一次修改的操作,一点小小的优化 } while(tim>qt){ if(ql<=c[tim].pos&&c[tim].pos<=qr) now-=!--cnt[a[c[tim].pos]]-!cnt[c[tim].col]++; swap(a[c[tim].pos],c[tim].col); tim--; } ans[q[i].id]=now; } for(RE int i=1;i<=cntq;i++){ Print(ans[i]); putchar('\n'); } return 0; }
想必你已经学会了带修莫队,如果你还没有了解过其他莫队算法看这里:
莫队(三):莫队算法的拓展