莫队

莫队

普通莫队

“只有普通莫队是莫涛提出的,其余都是后人根据不同情况 \(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;
}

树上莫队

还没学捏~

edit

posted @ 2023-03-29 17:00  雨夜风月  阅读(6)  评论(0编辑  收藏  举报