• 莫队算法


    基本莫队

    概述

    对于一个序列,如果有若干个区间询问,且对于每个询问【$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;
    } 
  • 相关阅读:
    正则表达式分组小记
    Python中关于try...finally的一些疑问
    hello,world!
    02操控奴隶——掌握它的语言“Python”
    01操控奴隶——奴隶的构成与运行方式
    vue特殊属性 key ref slot
    vue内置组件 transition 和 keep-alive 使用
    vue文档全局api笔记2
    vue文档全局api笔记1
    vue 二三倍图适配,1像素边框
  • 原文地址:https://www.cnblogs.com/bennettz/p/8520326.html
Copyright © 2020-2023  润新知