莫队算法

基本莫队

概述

对于一个序列,如果有若干个区间询问,且对于每个询问【$l$,$r$】可以从【$lpm 1$,$r$】或【$l$,$rpm 1$】快速得出,那么我们可以使用莫队算法

莫队算法的大体思路是将询问存下来,每个询问由上一个询问推出来
如果我们对于每个每个询问都由上一个询问推出来,每个询问的复杂度可以达到O(n),整体复杂度可以是O(n^2)
但是如果我们使用莫队,复杂度可以达到O((n+m)sqrt(n))。

具体实现

我们先对序列分块,每块长度为sqrt(n),然后以询问左端点所在的块为第一关键字,右端点的大小为第二关键字进行排序
然后每个区间由它的上一个区间推出来,虽然单次询问仍可能是O(n),
但是在块中每次询问l的移动最大为sqrt(n),r的移动总和最大为n,块与块之间的移动最大为n
所以总复杂度为O((n+m)sqrt(n))

大致的代码如下

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define maxn 50005
int block[maxn],si;
int ans[maxn];
struct Q{
    int l,r,id;
    bool operator <(const Q& a)const{//排序时以左端点所在块为第一关键字,以右端点为第二关键字 
        if(block[l]!=block[a.l])return l<a.l;
        return r<a.r;
    }
}a[maxn];
void revise(int l,int add){
}
int main(){
    int n,m;scanf("%d%d",&n,&m);si=sqrt(n);//si为每个块的大小 
    for(int i=1;i<=n;i++)block[i]=i/si;//算出每个块包含的区间 
    for(int i=0;i<m;i++)scanf("%d%d",&a[i].l,&a[i].r),a[i].id=i;//读入所有询问 
    sort(a,a+m);//按照询问左端点所在块和右端点排序 
    int l=1,r=0;
    for(int i=0;i<m;i++){//对于每个询问由上一个询问的区间转移到当前询问区间 
        while(l<a[i].l)revise(l++,-1);
        while(l>a[i].l)revise(--l,1);
        while(r<a[i].r)revise(++r,1);
        while(r>a[i].r)revise(r--,-1);
        ans[a[i].id]=?;
    }
    for(int i=0;i<m;i++)printf("%d
",ans[i]);//输出每个询问 
    return 0;
} 

例题

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

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define maxn 50005
#define LL long long
int col[maxn],sum[maxn],block[maxn],si;
LL squ[maxn],ans1[maxn],ans2[maxn],ans;
struct Q{
    int l,r,id;
    bool operator <(const Q& a)const{
        if(block[l]!=block[a.l])return l<a.l;
        return r<a.r;
    }
}a[maxn];
LL gcd(LL a,LL b){
    return b?gcd(b,a%b):a;
}
void revise(int l,int add){
    ans-=squ[sum[col[l]]];sum[col[l]]+=add;ans+=squ[sum[col[l]]];
}
int main(){
    int n,m;scanf("%d%d",&n,&m);si=sqrt(n);
    for(int i=1;i<=n;i++)scanf("%d",col+i),block[i]=i/si,squ[i]=1ll*i*i;
    for(int i=0;i<m;i++)scanf("%d%d",&a[i].l,&a[i].r),a[i].id=i;//读入所有询问 
    sort(a,a+m);//按照询问左端点所在块和右端点排序 
    int l=1,r=0;
    for(int i=0;i<m;i++){//对于每个询问由上一个询问的区间转移到当前询问区间 
        while(l<a[i].l)revise(l++,-1);
        while(l>a[i].l)revise(--l,1);
        while(r<a[i].r)revise(++r,1);
        while(r>a[i].r)revise(r--,-1);
        ans1[a[i].id]=ans-(a[i].r-a[i].l+1);ans2[a[i].id]=1ll*(a[i].r-a[i].l+1)*(a[i].r-a[i].l);
        //本来ans1=sigma(C[sum[col[i]]][2])=sigma(sum[col[i]]*(sum[col[i]]-1))的,但它=sigma(sum[col[i]]^2-sum[col[i]])
        //ans=sigma(sum[col[i]]^2)且 sigma(sum[col[i]])=(a[i].r-a[i].l+1)所以ans1[a[i].id]=ans-(a[i].r-a[i].l+1)
        LL g=gcd(ans1[a[i].id],ans2[a[i].id]);ans1[a[i].id]/=g,ans2[a[i].id]/=g;
    }
    for(int i=0;i<m;i++)printf("%lld/%lld
",ans1[i],ans2[i]);//输出每个询问 
    return 0;
} 

带修改的莫队

如果询问之间还插入了修改,那么普通莫队的复杂度就会加上m*t(转移询问时还要加上修改,每两个询问之间的修改都可能是t)
于是我们要对普通的莫队做出一些修改

我们先对序列分块,每块长度为$n^{frac{2}{3}}$,然后以询问左端点所在的块为第一关键字,右端点所在的块为第二关键字,询问之前的修改次数为第三关键字进行排序
每次询问时不仅要移动l,r,也要移动t。

我们的排序将询问按左端点所在的块分成了$n^{frac{1}{3}}$个大块,每个大块又按右端点所在的块分成了$n^{frac{1}{3}}$个小块
总共有$n^{frac{2}{3}}$个小块,每个小块中t的移动总和最多为修改的总次数st,复杂度$O(n^{frac{2}{3}}t)$
每个大块中r的移动总和最多为n,复杂度$O(n^{frac{1}{3}}n)=O(n^{frac{4}{3}})$
每次l的移动最多是$n^{frac{2}{3}}$,复杂度$O(n^{frac{2}{3}}m)$
如果n,m,t是同一级别的,则总复杂度为$O(n^{frac{5}{3}})$

例题

bzoj2120: 数颜色

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define maxn 10005
#define maxt 1005
int ans[maxn],col[maxn],block[maxn],si,st=-1,apper[maxn*100],ans1,l=1,r=0,now[maxn];
struct Q{//询问 
    int l,r,t,id;
    bool operator < (const Q &a)const{//排序的第一关键字为l所在块,第二关键字为r所在块,第三关键字为询问之前的修改次数t 
        return (block[l]!=block[a.l])?l<a.l:(block[a.r]!=block[r])?r<a.r:t<a.t;
    }
}a[maxn];
struct C{//修改 
    int pos,New,old;
}op[maxt];
void change(int x,int y){
    if(x>=l&&x<=r){
        apper[col[x]]--;
        if(!apper[col[x]])ans1--;
        if(!apper[y])ans1++;
        apper[y]++;
    }
    col[x]=y;
}
void revise(int x,int add){
    apper[col[x]]+=add;
    if(add>0)ans1+=apper[col[x]]==1;
    else ans1-=!apper[col[x]];
}
int main(){
    int n,m,size=0;scanf("%d%d",&n,&m);size=pow(n,2/3.0);//每块范围大小为n^(2/3)
    char s[2];
    for(int i=1;i<=n;i++)scanf("%d",col+i),now[i]=col[i],block[i]=i/size;//读入原始序列并计算出每个块所包含的区间 
    for(int i=0;i<m;i++){
        scanf("%s",s);
        if(s[0]=='Q'){
            scanf("%d%d",&a[si].l,&a[si].r);
            a[si].t=st,a[si].id=si++;
        }
        else{
            scanf("%d%d",&op[st].pos,&op[++st].New);
            op[st].old=now[op[st].pos];
            now[op[st].pos]=op[st].New;
        }
    }
    sort(a,a+si);
    int t=-1;
    for(int i=0;i<si;i++){
        while(t<a[i].t)change(op[t].pos,op[++t].New);
        while(t>a[i].t)change(op[t--].pos,op[t].old);
        while(l<a[i].l)revise(l++,-1);
        while(l>a[i].l)revise(--l,1);
        while(r<a[i].r)revise(++r,1);
        while(r>a[i].r)revise(r--,-1);
        ans[a[i].id]=ans1;
    }
    for(int i=0;i<si;i++)printf("%d
",ans[i]);
    return 0;
} 

树上莫队

将树进行分块,然后和普通莫队一样进行排序和修改。

分块方法为王室联邦分块,虽然王室联邦分块的每个不一定连通,
但是它的大小,数量都能保证,且同一个块内的元素的距离$le$块的大小

例题【WC2013】糖果公园-树上带修改莫队

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define maxn 100005
#define LL long long
int size,si,block[maxn],val[maxn],w[maxn],col[maxn],now[maxn];
struct Q{
    int x,y,t,id;
    bool operator <(const Q &b)const{//排序的第一关键字为x所在块,第二关键字为y所在块,第三关键字为询问之前的修改次数t 
        return (block[x]!=block[b.x])?block[x]<block[b.x]:(block[y]!=block[b.y]?block[y]<block[b.y]:t<b.t);
    }
}q[maxn];
struct C{
    int old,ne,x;
}ch[maxn];
struct Edge{
    int next,to;
}edge[maxn*2];
int fi[maxn],se,fa[maxn][18],stack[maxn],t,depth[maxn];
inline void add_edge(int u,int v){
    edge[++se].next=fi[u],fi[u]=se,edge[se].to=v,
    edge[++se].next=fi[v],fi[v]=se,edge[se].to=u;
}
void out(int s){
    si++;
    while(t>s)block[stack[--t]]=si;
}
void dfs(int x,int s){ 
    for(int i=1;i<18;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=fi[x];i;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa[x][0])continue;
        fa[v][0]=x,depth[v]=depth[x]+1,dfs(v,t);
        if(t-s>size)out(s);//如果当前子树栈中节点>=size,就自成一块 
    }
    stack[t++]=x;
    
}
void up(int &x,int d){
    for(int i=17;i>=0;i--){
        if(d&(1<<i))x=fa[x][i];
    }
}
int lca(int u,int v){
    if(depth[u]>depth[v])swap(u,v);
    up(v,depth[v]-depth[u]);
    for(int i=17;i>=0;i--){
        if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
    }
    if(u!=v)return fa[u][0];
    return u;
}
bool vis[maxn];
LL ans,ans1[maxn];int num[maxn];
void update(int x){//更新x(原来在路径上就删掉,不在就加入) 
    if(vis[x]){
        vis[x]=0;
        ans-=1ll*w[num[col[x]]--]*val[col[x]];
    }
    else{
        vis[x]=1;
        ans+=1ll*w[++num[col[x]]]*val[col[x]];
    }
}
void change(int x,int y){//将col[x]改成y 
    while(x!=y){
        if(depth[x]<depth[y])update(y),y=fa[y][0];
        else update(x),x=fa[x][0];
    }
}
void modify(int x,int y){//将x到y上的所有节点更新 
    if(!vis[x])col[x]=y;
    else{
        update(x);
        col[x]=y;
        update(x);
    }
}
int main(){
    int n,m,q1,u,v,op,t=0,cnt=0;
    scanf("%d%d%d",&n,&m,&q1);size=pow(n,0.6);
    for(int i=1;i<=m;i++)scanf("%d",val+i);
    for(int i=1;i<=n;i++)scanf("%d",w+i);
    for(int i=1;i<n;i++)scanf("%d%d",&u,&v),add_edge(u,v);
    for(int i=1;i<=n;i++)scanf("%d",col+i),now[i]=col[i];
    depth[1]=1,dfs(1,0);
    while(t>0)block[stack[--t]]=si;//将最后剩下的节点加入最后一个块
    for(int i=0;i<q1;i++){
        scanf("%d%d%d",&op,&u,&v);
        if(op){
            if(block[u]>block[v])swap(u,v);//小优化,将所有询问x的块都变的比y小以防止query(u,v),query(v,u)算两次的情况 
            q[cnt].x=u,q[cnt].y=v,q[cnt].t=t,q[cnt].id=cnt;cnt++;
        }
        else{
            ch[t].x=u,ch[t].old=now[u],ch[t++].ne=v,now[u]=v;
        }
    }
    sort(q,q+cnt);u=v=1,t=0;
    for(int i=0;i<cnt;i++){
        while(t<q[i].t)modify(ch[t].x,ch[t].ne),t++;
        while(t>q[i].t)t--,modify(ch[t].x,ch[t].old);
        change(u,q[i].x);change(v,q[i].y);u=q[i].x,v=q[i].y;
        int Lca=lca(q[i].x,q[i].y);
        update(Lca);
        ans1[q[i].id]=ans;
        update(Lca);
    }
    for(int i=0;i<cnt;i++){
        printf("%lld
",ans1[i]);
    }
    return 0;
} 
原文地址:https://www.cnblogs.com/bennettz/p/8520326.html