https://blog.csdn.net/niiick/java/article/details/80327593
就是用一个cnt[]数组记录当前每个数字出现了多少次
然后用左L,右R指针不断移动来获得当前区间的cnt[]数组
增加时每当有一个cnt[i]==1就代表新增加了一个数字
删除时每当有一个cnt[i]==0就代表新减少了一个数字
但是既然都说了这是一个暴力,这样当然是过不了
不过这就是莫队算法的其中一个核心了
考虑到这样做会T的原因是左右指针移动次数无法确定‘
所以我们可以改变对询问的处理顺序,采用离线算法
具体怎么改变询问顺序呢
我们将整个序列按分块思想分成sqrt(N)块,每块有sqrt(N)个元素
然后将询问排序,具体方法是
以询问左区间L所在块为第一关键字
询问右区间R为第二关键字排序
现在所有询问左区间所在块是单调递增的
所以左指针移动次数不超过M*sqrt(N)次
由于同一个块内所有询问右指针单调递增
一个块内的询问右指针移动最大为N, NN,总共sqrt(N)个块
所以右指针移动次数不超过N*?sqrt(N)次
整体复杂度O((N+M) *sqrt(N)
模板题:BZOJ1878 [SDOI2009]HH的项链
#include<iostream> #include<cmath> #include<algorithm> #include<queue> #include<cstring> #include<cstdio> using namespace std; int read() { int f=1,x=0; char ss=getchar(); while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();} while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();} return x*f; } int t,n,m; int L=1,R=0,a[500010]; int cnt[1000010]; struct node{int ll,rr,num;}q[200010]; bool cmp(node a,node b){return (a.ll/t)==(b.ll/t) ?a.rr<b.rr :(a.ll/t)<(b.ll/t);} int ans[200010],sum; void add(int x) { cnt[x]++; if(cnt[x]==1) sum++; } void del(int x) { cnt[x]--; if(cnt[x]==0) sum--; } int main() { n=read(); for(int i=1;i<=n;++i) a[i]=read(); m=read(); for(int i=1;i<=m;++i) q[i].ll=read(),q[i].rr=read(),q[i].num=i; t=sqrt(n);//块数 sort(q+1,q+1+m,cmp); for(int i=1;i<=m;++i) { while(R<q[i].rr) add(a[++R]); while(R>q[i].rr) del(a[R--]); while(L<q[i].ll) del(a[L++]); while(L>q[i].ll) add(a[--L]); ans[q[i].num]=sum; } for(int i=1;i<=m;++i) printf("%d\n",ans[i]); return 0; }
带修改的莫队
同样先看问题
Q:有一个长为N序列,有M个操作:
查询操作,查询在区间[L,R]内,出现了多少个不同的数字。
修改操作,修改第x个数字为y
(数字范围为0到1000000之间的整数),N ≤ 50000,M ≤ 50000。
带上了修改的思路依旧很暴力
对于每个询问记录在这个询问前修改了多少次
改多了或改少了都一个while改回来
不过这里分块要变成
每块大小N^(2/3) ,总共N^(1/3)块
且要加入修改顺序为第三关键字排序
这样左右指针,修改指针移动次数都是N∗N^(3/2)
所以整个算法渐进复杂度O(N^(5/3)
#include<iostream> #include<cmath> #include<algorithm> #include<queue> #include<cstring> #include<cstdio> using namespace std; int read() { int f=1,x=0; char ss=getchar(); while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();} while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();} return x*f; } const int maxn=50010; int n,m,t; int a[maxn],cnt[2000010]; int cntq,cntc; int L=1,R=0,cur,sum; struct node{int ll,rr,num,id,pre;}q[maxn]; struct node2{int pos,val;}c[maxn]; int ans[maxn]; bool cmp(node a,node b) { if(a.ll/t!=b.ll/t) return a.ll/t<b.ll/t;//左端点按块 if(a.rr/t!=b.rr/t) { if((a.ll/t)&1) return a.rr<b.rr;//右端点奇偶块排序优化 else return a.rr>b.rr; } return a.pre<b.pre;//修改顺序排 } void add(int x) { cnt[x]++; if(cnt[x]==1) sum++; } void del(int x) { cnt[x]--; if(cnt[x]==0) sum--; } void update(int cur) { if(c[cur].pos>=L&&c[cur].pos<=R)//修改在当前查询区间里才要更新 { if(--cnt[ a[ c[cur].pos ] ]==0) sum--; if(++cnt[ c[cur].val ]==1) sum++; } swap(c[cur].val,a[c[cur].pos]);//因为可能多次更改,所以直接交换要改的数 } int main() { n=read();m=read(); t=pow(n,2.0/3);//**高亮**,注意分块大小 for(int i=1;i<=n;++i) a[i]=read(); for(int i=1;i<=m;++i) { char ss; scanf("%s",&ss); if(ss==‘Q‘) { q[++cntq].ll=read(); q[cntq].rr=read(); q[cntq].pre=cntc; //记录上一次修改编号 q[cntq].num=cntq; } else if(ss==‘R‘) c[++cntc].pos=read(),c[cntc].val=read();//记录修改 } sort(q+1,q+1+cntq,cmp); for(int i=1;i<=cntq;++i) { while(R<q[i].rr) add(a[++R]); while(R>q[i].rr) del(a[R--]); while(L<q[i].ll) del(a[L++]); while(L>q[i].ll) add(a[--L]); while(cur<q[i].pre) update(++cur);//把改多/少的改回来 while(cur>q[i].pre) update(cur--); ans[q[i].num]=sum; } for(int i=1;i<=cntq;++i) printf("%d\n",ans[i]); return 0; }
原文:https://www.cnblogs.com/cutemush/p/12861029.html