莫队初步

参考blog

了解学习了一下莫队,果然是个很好用的东西啊,我为什么现在才学…

用途

处理一些离线的区间问题

一般来说,令 ansi,j 表示询问区间 [i,j] 的答案,则 ansi,j 可以在常数的复杂度内由 ansi1,j ansi+1,j ansi,j1 ansi,j+1 推得

例题

2038: [2009国家集训队]小Z的袜子(hose)

简单推一下可以发现很好转移,对于移动的那一端只要在分子里修改:把C(2,i)改成C(2,i+1||i-1)就行了

#include<cstdio>
#include<cmath>
#include<algorithm>
#define N 50005
#define LL long long
using namespace std;

struct node{
    int l,r,id;
    LL a,b;
}a[N];
int n,m,c[N],pos[N];
LL s[N],ans;

bool cmp1(node a,node b){
    return (pos[a.l]<pos[b.l])||(pos[a.l]==pos[b.l]&&(pos[a.l]&1?a.r<b.r:a.r>b.r));
}

bool cmp2(node a,node b){return a.id<b.id;}

LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);}

inline LL pow(LL x){return x*x;}

void update(int x,int opt){
    ans-=pow(s[c[x]]);
    s[c[x]]+=opt;
    ans+=pow(s[c[x]]);
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) scanf("%d",&c[i]);
    int block=int(sqrt(n));
    for(int i=1;i<=n;++i)
        pos[i]=(i-1)/block+1;
    for(int i=1;i<=m;++i) scanf("%d%d",&a[i].l,&a[i].r),a[i].id=i;
    sort(a+1,a+m+1,cmp1);
    for(int i=1,l=1,r=0;i<=m;++i){
        for(;r<a[i].r;++r) update(r+1,1);
        for(;r>a[i].r;--r) update(r,-1);
        for(;l<a[i].l;++l) update(l,-1);
        for(;l>a[i].l;--l) update(l-1,1);
        if(a[i].l==a[i].r){
            a[i].a=0,a[i].b=1;
            continue;
        }
        a[i].a=ans-(a[i].r-a[i].l+1);
        a[i].b=1ll*(a[i].r-a[i].l+1)*(a[i].r-a[i].l);
        LL tmp=gcd(a[i].a,a[i].b);
        a[i].a/=tmp,a[i].b/=tmp;
    }
    sort(a+1,a+m+1,cmp2);
    for(int i=1;i<=m;++i)
        printf("%lld/%lld\n",a[i].a,a[i].b);
    return 0;
}

3781: 小B的询问

区间头上加减一下平方就好了

#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 50005
#define LL long long
using namespace std;

struct query{
    int l,r,id;
}q[N];
int n,m,k,a[N],c[N],bl[N];
LL res,ans[N];

inline int read(){
    int a=0;char f=1,c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
    return a*f;
}

inline LL pow(LL x){return x*x;}

bool cmp(query a,query b){
    return (bl[a.l]<bl[b.l])||(bl[a.l]==bl[b.l]&&((bl[a.l]&1)?a.r<b.r:a.r>b.r));
}

inline void update(int x,int add){
    res-=pow(c[a[x]]);
    c[a[x]]+=add;
    res+=pow(c[a[x]]);
}

int main(){
    n=read(),m=read(),k=read();
    int sz=sqrt(n);
    for(int i=1;i<=n;++i) a[i]=read(),bl[i]=(i-1)/sz+1;
    for(int i=1;i<=m;++i){
        q[i].l=read(),q[i].r=read();
        q[i].id=i;
    }
    sort(q+1,q+m+1,cmp);
    for(int i=1,l=1,r=0;i<=m;++i){
        for(;r<q[i].r;++r) update(r+1,1);
        for(;r>q[i].r;--r) update(r,-1);
        for(;l<q[i].l;++l) update(l,-1);
        for(;l>q[i].l;--l) update(l-1,1);
        ans[q[i].id]=res;
    }
    for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
    return 0;
}

4542: [Hnoi2016]大数

结论:设 ai 为第 i 位及之后的数构成的数,如121121中,a1=121121,a4=121.则若 ai(modP)==aj(modP) ,且 i<j ,那么i~j-1的数构成的数为 P 的倍数,问题转化为求[l,r+1]中有几对相同的数

题目没给模数范围,其实是超int了,所以离散处理一下

对于P=2 P=5 的情况另外处理

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 100005
#define LL long long
using namespace std;

struct query{
    int l,r,id;
    LL ans;
}q[N];
int n,m,s[N],num,bl[N],c[N];
LL a[N],b[N],pw[N],P,ans;

bool cmp(query a,query b){return (bl[a.l]<bl[b.l])||(bl[a.l]==bl[b.l]&&(bl[a.l]&1?a.r<b.r:a.r>b.r));}

bool cmp1(query a,query b){return a.id<b.id;}

void update(int x,int add){
    if(a[b[x]]>1) ans-=(a[b[x]]-1)*a[b[x]]/2;
    a[b[x]]+=add;
    if(a[b[x]]>1) ans+=(a[b[x]]-1)*a[b[x]]/2;
}

int main(){
    scanf("%lld",&P);
    char ch[N];
    scanf("%s",ch+1);
    n=strlen(ch+1);
    for(int i=1;i<=n;++i) s[i]=ch[i]-'0',pw[i]=i==1?1:(pw[i-1]*10)%P;
    LL tmp=0;
    for(int i=n;i>=1;--i){
        tmp=(tmp+pw[n-i+1]*s[i])%P;
        a[i]=b[i]=tmp;
    }
    sort(a+1,a+n+1);
    num=unique(a+1,a+n+1)-(a+1);
    for(int i=1;i<=n+(a[1]==0);++i){
        b[i]=lower_bound(a+1,a+num+1,b[i])-a;
        c[i]=a[i];
    }
    memset(a,0,sizeof(a));
    int sz=sqrt(n);
    for(int i=1;i<=n;++i) bl[i]=(i-1)/sz+1;
    scanf("%d",&m);
    for(int i=1;i<=m;++i)
        scanf("%d%d",&q[i].l,&q[i].r),++q[i].r,q[i].id=i;
    sort(q+1,q+m+1,cmp);
    if(P==2||P==5){
        for(int i=1;i<=n;++i)
            if(s[i]%P==0) a[i]=i,b[i]=1;
            else a[i]=b[i]=0;
            for(int i=1;i<=n;++i) a[i]+=a[i-1],b[i]+=b[i-1];
        for(int i=1;i<=m;++i) --q[i].r,q[i].ans=a[q[i].r]-a[q[i].l-1]-(b[q[i].r]-b[q[i].l-1])*(q[i].l-1);
    }
    else
        for(int i=1,l=1,r=0;i<=m;++i){
            for(;r<q[i].r;++r) update(r+1,1);
            for(;r>q[i].r;--r) update(r,-1);
            for(;l<q[i].l;++l) update(l,-1);
            for(;l>q[i].l;--l) update(l-1,1);
            q[i].ans=ans;
        }
    sort(q+1,q+m+1,cmp1);
    for(int i=1;i<=m;++i)
        printf("%d\n",q[i].ans);
    return 0;
}

2120: 数颜色

这道题做法很多,我用分块也写过,还没有用数据结构写过…

用莫队做的话就是维护一下每个询问操作之前最近的一个修改设为 pre

然后正常莫队过程中对于每一个询问,只要把修改操作通过执行或撤销,使目前的状态就是询问时的状态即可

#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 10005
#define M 1000005
using namespace std;

struct C{
    int pos,c;
}c[N];
struct Q{
    int l,r,id,lst;
}q[N];
int n,m,m0,m1,res,l,r,a[N],bl[N],mark[M],ans[N];

inline int read(){
    int a=0;char f=1,c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
    return a*f;
}

inline bool cmp(Q a,Q b){
    return bl[a.l]<bl[b.l]||(bl[a.l]==bl[b.l]&&((bl[a.l]&1)?a.r<b.r:a.r>b.r));
}

inline void change(C &x){
    if(x.pos>=l&&x.pos<=r){
        if(--mark[a[x.pos]]==0) --res;
        if(++mark[x.c]==1) ++res;
    }
    swap(a[x.pos],x.c);
}

inline void update(int x,int add){
    if(add==1) if(++mark[a[x]]==1) ++res;
    if(add==-1) if(--mark[a[x]]==0) --res;
}

int main(){
    n=read(),m=read();
    int sz=sqrt(n);
    for(int i=1;i<=n;++i) a[i]=read(),bl[i]=(i-1)/sz+1;
    for(int i=1;i<=m;++i){
        char ch=getchar();
        while(ch!='Q'&&ch!='R')
            ch=getchar();
        int x=read(),y=read();
        if(ch=='R') c[++m0]=(C){x,y};
        else q[++m1]=(Q){x,y,i,m0};     //m1之前有m0次修改 
    }
    sort(q+1,q+m1+1,cmp);
    int nw=0;
    l=1,r=0;
    for(int i=1;i<=m1;++i){
        for(;nw<q[i].lst;++nw) change(c[nw+1]);
        for(;nw>q[i].lst;--nw) change(c[nw]);
        for(;r<q[i].r;++r) update(r+1,1);
        for(;r>q[i].r;--r) update(r,-1);
        for(;l<q[i].l;++l) update(l,-1);
        for(;l>q[i].l;--l) update(l-1,1);
        ans[q[i].id]=res;
    }
    for(int i=1;i<=m;++i)
        if(ans[i]) printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值