莫队
莫队
普通莫队
“只有普通莫队是莫涛提出的,其余都是后人根据不同情况 \(YY\) 得出的。”
排序
一般以 左端点所在块编号 为第一关键字、右端点 为第二关键字排序,块长一般取 \(\sqrt{n}\)
bool operator<(cs qwq o)cs{
return b[l]==b[o.l]?r<o.r:l<o.l;
}
一种小优化是奇偶化排序
即奇数块右端点升序、偶数块右端点降序排序
bool operator<(cs qwq o)cs{
return b[l]==b[o.l]?((b[l]&1)?r<o.r:r>o.r):l<o.l;
}
统计答案
暴力从上一个区间移动到下一个区间
for(ri int i=1,l=q[1].l,r=l-1;i<=m;++i){
while(r<q[i].r) add(++r);
while(l>q[i].l) add(--l);
while(r>q[i].r) del(r--);
while(l<q[i].l) del(l++);
as[q[i].id]=ans;
}
如果移动一次可以 \(O(1)\) 更新答案,则总复杂度为 \(O(n\sqrt{n})\)
模板题:小B的询问
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
namespace Q{
il int rd(){
ri int x=0;ri bool f=0;ri char c=getchar();
while(~c&&!isdigit(c)) f|=(c==45),c=getchar();
while(isdigit(c)) x=x*10+(c^48),c=getchar();
return f?-x:x;
}
il void wt(int x){
if(x<0) x=-x,putchar(45);
if(x>=10) wt(x/10);
return putchar(x%10|48),void();
}
} using namespace Q;
cs int N=5e4+5;
int n,m,k,a[N],cnt[N],b[N],as[N],ans;
struct qwq{
int l,r,id;
bool operator<(cs qwq o)cs{
return b[l]==b[o.l]?((b[l]&1)?r<o.r:r>o.r):l<o.l;
}
}q[N];
il void add(cs int x){ans+=2*(++cnt[a[x]])-1;}
il void del(cs int x){ans-=2*(--cnt[a[x]])+1;}
signed main(){
n=rd(),m=rd(),k=rd(),k=sqrt(n);
for(ri int i=1;i<=n;++i) a[i]=rd(),b[i]=(i-1)/k+1;
for(ri int i=1;i<=m;++i) q[i].id=i,q[i].l=rd(),q[i].r=rd();
sort(q+1,q+1+m);
for(ri int i=1,l=q[1].l,r=l-1;i<=m;++i){
while(r<q[i].r) add(++r);
while(l>q[i].l) add(--l);
while(r>q[i].r) del(r--);
while(l<q[i].l) del(l++);
as[q[i].id]=ans;
}
for(ri int i=1;i<=m;++i) wt(as[i]),putchar(10);
return 0;
}
带修莫队
带修莫队就是在普通莫队的基础上加一维时间,统计答案前先进行修改
排序
排序变为以 左端点所在块编号 为第一关键字、右端点所在块编号 为第二关键字、时间 为第三关键字排序,块长一般取 \(\sqrt[3]{n^2}\)
bool operator<(cs qwq o)cs{
return b[l]==b[o.l]?b[r]==b[o.r]?t<o.t:r<o.r:l<o.l;
}
统计答案
对于查询操作
如果当前改的比本次查询需要改的少,就改过去
反之如果比本次查询需要改的多,就改回来
具体的,记当前修改到 \(t\),本次查询需要修改到 \(qt\)
若在 \((t,qt)\) 这个时间段内有需要修改的点(到了它修改的时间)还未修改,进行修改
若在 \((qt,t]\) 这个时间段内有不需要修改的点(未到它修改的时间)给修改了,撤销修改
for(ri int i=1,t=0,L=1,R=0;i<=cq;++i){
while(R<q[i].r) add(++R);
while(L>q[i].l) add(--L);
while(R>q[i].r) del(R--);
while(L<q[i].l) del(L++);
while(t<q[i].t) upd(++t,i);
while(t>q[i].t) upd(t--,i);
as[q[i].id]=ans;
}
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
namespace Q{
il int rd(){
ri int x=0;ri bool f=0;ri char c=getchar();
while(~c&&!isdigit(c)) f|=(c==45),c=getchar();
while(isdigit(c)) x=x*10+(c^48),c=getchar();
return f?-x:x;
}
il void wt(int x){
if(x<0) x=-x,putchar(45);
if(x>=10) wt(x/10);
return putchar(x%10|48),void();
}
il bool gc(){
ri char c=getchar();
while(~c&&c^'Q'&&c^'R') c=getchar();
return c=='Q';
}
} using namespace Q;
cs int N=133335,A=1e6+5;
int n,m,b[N],cnt[A],col[N],cq,co,as[N],ans,k;
struct qwq{
int l,r,id,t;
bool operator<(cs qwq o)cs{
return b[l]==b[o.l]?b[r]==b[o.r]?t<o.t:r<o.r:l<o.l;
}
}q[N];
struct ovo{int pos,col;}o[N];
il void add(cs int x){ans+=((++cnt[col[x]])==1);}
il void del(cs int x){ans-=(!(--cnt[col[x]]));}
il void upd(cs int x,cs int i){
if(o[x].pos>=q[i].l&&o[x].pos<=q[i].r){
ans-=(!(--cnt[col[o[x].pos]]));
ans+=((++cnt[o[x].col])==1);
}
swap(col[o[x].pos],o[x].col);
}
signed main(){
n=rd(),m=rd(),k=pow(n,2.0/3.0);
for(ri int i=1;i<=n;++i) col[i]=rd(),b[i]=(i-1)/k+1;
for(ri int i=1;i<=m;++i){
if(gc()) q[++cq].l=rd(),q[cq].r=rd(),q[cq].id=cq,q[cq].t=co;
else o[++co].pos=rd(),o[co].col=rd();
}
sort(q+1,q+1+cq);
for(ri int i=1,t=0,L=1,R=0;i<=cq;++i){
while(R<q[i].r) add(++R);
while(L>q[i].l) add(--L);
while(R>q[i].r) del(R--);
while(L<q[i].l) del(L++);
while(t<q[i].t) upd(++t,i);
while(t>q[i].t) upd(t--,i);
as[q[i].id]=ans;
}
for(ri int i=1;i<=cq;++i) wt(as[i]),putchar(10);
return 0;
}
回滚莫队
有时候加或减的操作不好实现,考虑回滚莫队
排序
仍以 左端点所在块编号 为第一关键字、右端点 为第二关键字排序
这里就不能奇偶化排序了
bool operator<(cs qwq o)cs{
return b[l]==b[o.l]?r<o.r:l<o.l;
}
统计答案
由于删除操作不好实现,所以考虑不删除,即不删除莫队 (不加莫队也类似,这里不再赘述)
考虑对于左右端点在同一块内的询问暴力计算
对于其余询问,每次统计结束将左端点移至所在块最右端
for(ri int bl=1,i=1,l,r,s,L;bl<=b[n];++bl){
L=min(n,bl*k),l=L+1,r=L,ans=0;
for(;b[q[i].l]==bl;++i){
if(b[q[i].r]==bl){forc(i);continue;}
while(r<q[i].r) addr(++r);s=ans;
while(l>q[i].l) addl(--l);as[q[i].id]=ans;
while(l<=L) res(l++);ans=s;
}
clear();
}
模板题:【模板】回滚莫队&不删除莫队
移动左右端点的操作可以有区别,主要细节和常数
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
namespace Q{
il int rd(){
ri int x=0;ri bool f=0;ri char c=getchar();
while(~c&&!isdigit(c)) f|=(c==45),c=getchar();
while(isdigit(c)) x=x*10+(c^48),c=getchar();
return f?-x:x;
}
il void wt(int x){
if(x<0) x=-x,putchar(45);
if(x>=10) wt(x/10);
return putchar(x%10|48),void();
}
} using namespace Q;
cs int N=2e5+5;
int n,m,k,a[N],b[N],as[N],ans,clr[N],c,cnt[N],fir[N],lst[N];
struct qwq{
int l,r,id;
bool operator<(cs qwq o)cs{
return b[l]==b[o.l]?r<o.r:l<o.l;
}
}q[N];
il void forc(int o){
for(ri int i=q[o].l;i<=q[o].r;++i) cnt[a[i]]=0;
for(ri int i=q[o].l;i<=q[o].r;++i){
if(!cnt[a[i]]) cnt[a[i]]=i;
else as[q[o].id]=max(as[q[o].id],i-cnt[a[i]]);
}
}
il void addl(int x){
if(!lst[a[x]]) lst[a[x]]=x;
ans=max(ans,lst[a[x]]-x);
}
il void addr(int x){
if(!fir[a[x]]) fir[a[x]]=x,clr[++c]=a[x];
lst[a[x]]=x,ans=max(ans,x-fir[a[x]]);
}
il void res(int x){if(lst[a[x]]==x) lst[a[x]];}
il void clear(){while(c) lst[clr[c]]=fir[clr[c--]]=0;}
signed main(){
n=rd(),k=sqrt(n);
for(ri int i=1;i<=n;++i) cnt[i]=a[i]=rd(),b[i]=(i-1)/k+1;
sort(cnt+1,cnt+1+n),m=unique(cnt+1,cnt+1+n)-cnt-1;
for(ri int i=1;i<=n;++i) a[i]=lower_bound(cnt+1,cnt+1+m,a[i])-cnt;
m=rd();
for(ri int i=1;i<=m;++i) q[i].id=i,q[i].l=rd(),q[i].r=rd();
sort(q+1,q+1+m);
for(ri int bl=1,i=1,l,r,s,L;bl<=b[n];++bl){
L=min(n,bl*k),l=L+1,r=L,ans=0;
for(;b[q[i].l]==bl;++i){
if(b[q[i].r]==bl){forc(i);continue;}
while(r<q[i].r) addr(++r);s=ans;
while(l>q[i].l) addl(--l);as[q[i].id]=ans;
while(l<=L) res(l++);ans=s;
}
clear();
}
for(ri int i=1;i<=m;++i) wt(as[i]),putchar(10);
return 0;
}
其实歴史の研究更模板一些,统计答案更简单而且移动左右端点的操作一致 (还不卡常)
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
#define int long long
using namespace std;
namespace Q{
il int rd(){
ri int x=0;ri bool f=0;ri char c=getchar();
while(!isdigit(c)) f|=(c==45),c=getchar();
while(isdigit(c)) x=x*10+(c^48),c=getchar();
return f?-x:x;
}
il void wt(int x){
if(x<0) x=-x,putchar(45);
if(x>=10) wt(x/10);
return putchar(x%10|48),void();
}
} using namespace Q;
cs int N=1e5+5;
int n,m,k,b[N],a[N],as[N],cnt[N],s,clr[N],c;
struct ovo{
int x,rk;
}p[N];
struct qwq{
int l,r,id;
}q[N];
il bool cmp(cs qwq o,cs qwq o2){
return b[o.l]==b[o2.l]?o.r<o2.r:o.l<o2.l;
}
il int calc(cs int l,cs int r){
ri int res=0;
for(ri int i=l;i<=r;++i) a[p[i].rk]=0;
for(ri int i=l;i<=r;++i){
a[p[i].rk]++,res=max(res,a[p[i].rk]*p[i].x);
}
return res;
}
il void add(int x){
cnt[p[x].rk]++;
s=max(s,cnt[p[x].rk]*p[x].x);
}
signed main(){
n=rd(),m=rd(),k=sqrt(n);
for(ri int i=1;i<=n;++i){
a[i]=p[i].x=rd();
b[i]=(i-1)/k+1;
}
sort(a+1,a+1+n);
c=unique(a+1,a+1+n)-a-1;
for(ri int i=1;i<=n;++i){
p[i].rk=lower_bound(a+1,a+1+c,p[i].x)-a;
}
for(ri int i=1;i<=m;++i){
q[i].l=rd(),q[i].r=rd(),q[i].id=i;
}
sort(q+1,q+1+m,cmp);
for(ri int i=1,l=1,L,R,ll,ls;l<=b[n];++l){
ll=min(n,l*k),L=ll+1,R=ll,s=0;
memset(cnt,0,sizeof(cnt));
for(;l==b[q[i].l];++i){
if(b[q[i].r]==l){
as[q[i].id]=calc(q[i].l,q[i].r);
continue;
}
while(R<q[i].r) add(++R);
ls=s;
while(L>q[i].l) add(--L);
as[q[i].id]=s;
while(L<=ll) --cnt[p[L++].rk];
s=ls;
}
}
for(ri int i=1;i<=m;++i) wt(as[i]),putchar(10);
return 0;
}
树上莫队
还没学捏~
I went to the woods because I wanted to live deliberately, I wanted to live deep and suck out all the marrow of life, and not when I had come to die, discover that I had not live.