莫队


莫队


分块简介

对序列进行根号n的拆分,接下来每一段做懒标记和单独处理,对于跨块的操作可以对整个大块进行操作,对于小块我们直接暴力修改,因此时间复杂度可以降低至m*根号n。

基本线段树改分块

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5+10,M = 350;
int n,m,len;
LL add[M],sum[M];
int w[N];
inline int get(int i)  {return (i-1)/len;}
void change(int l,int r,int d)
{
    if(get(l)==get(r))
        for(int i=l;i<=r;i++)   w[i]+=d,sum[get(i)]+=d;
    else{
        int i=l,j=r;
        while(get(i)==get(l))   w[i]+=d,sum[get(i)]+=d,i++;
        while(get(j)==get(r))   w[j]+=d,sum[get(j)]+=d,j--;
        for(int k=get(i);k<=get(j);k++) sum[k]+=len*d,add[k]+=d;
    }
}
LL query(int l,int r)
{
    LL res=0;
    if(get(l)==get(r))
    {
        for(int i=l;i<=r;i++)   res+=w[i]+add[get(i)];
        return res;
    }
    else{
        int i=l,j=r;
        while(get(i)==get(l))   res+=w[i]+add[get(i)],i++;
        while(get(j)==get(r))   res+=w[j]+add[get(j)],j--;
        for(int k=get(i);k<=get(j);k++) res+=sum[k];
    }
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    len=sqrt(n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&w[i]);
        sum[get(i)]+=w[i];
    }
    char op[2];
    int l,r,d;
    while(m--)
    {
        scanf("%s%d%d",op,&l,&r);
        if(*op=='C')
        {
            scanf("%d",&d);
            change(l,r,d);
        }
        else    printf("%lld\n",query(l,r));
    }
    return 0;
}

基础莫队

莫队是一种离线算法,他的思想是对双指针的进一步优化,利用分块的思想进行优化,一般可以解决对状态比较好维护的序列,可以进行O(1)的操作,或者利用回滚莫队进行回滚。

求区间的子区间中不同数的个数。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N = 5e4+10,M = 2e5+10,S = 1000010;
int n,m,len;
int w[N],ans[M];
struct Query{
    int id,l,r;
}q[M];
int cnt[S];
inline int get(int x)  {return x/len;}
bool cmp(const Query&a,const Query&b)
{
    int i=get(a.l),j=get(b.l);
    if(i!=j)    return i<j;
    return a.r<b.r;
}
void add(int x,int &res)
{
    if(!cnt[x]) res++;
    cnt[x]++;
}
void del(int x,int &res)
{
    cnt[x]--;
    if(!cnt[x]) res--;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)   scanf("%d",&w[i]);
    scanf("%d",&m);
    len=sqrt((double)n);
    for(int i=0;i<m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        q[i]=(Query){i,l,r};
    }
    sort(q,q+m,cmp);
    for(int k=0,i=0,j=1,res=0;k<m;k++)
    {
        int id=q[k].id,l=q[k].l,r=q[k].r;
        while(i<r)  add(w[++i],res);
        while(i>r)  del(w[i--],res);
        while(j<l)  del(w[j++],res);
        while(j>l)  add(w[--j],res);
        ans[id]=res;
    }
    for(int i=0;i<m;i++)    printf("%d\n",ans[i]);
    return 0;
}

带修改的莫队

在上题的基础上加上了修改的操作,引入了时间戳的概念,我们可以利用对时间戳增加另一个维度来维持修改询问。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
using namespace std;
const int N = 1e4+10,S = 1e6+10;
int n,m,mq,mc,len;
int w[N],cnt[S],ans[N];
struct Query{
    int id,l,r,t;
}q[N];
struct Modift{
    int p,c;
}c[N];
inline int get(int x)   {return x/len;}
bool cmp(const Query& a,const Query& b)
{
    int al=get(a.l),ar=get(a.r);
    int bl=get(b.l),br=get(b.r);
    if(al!=bl)  return al<bl;
    if(ar!=br)  return ar<br;
    return a.t<b.t;
}
void add(int x,int &res)
{
    if(!cnt[x]) res++;
    cnt[x]++;
}
void del(int x,int &res)
{
    cnt[x]--;
    if(!cnt[x]) res--;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)   scanf("%d",&w[i]);
    while(m--)
    {
        char op[2];
        int a,b;
        scanf("%s%d%d",op,&a,&b);
        if(*op=='Q')    mq++,q[mq]=(Query){mq,a,b,mc};
        else mc++,c[mc]=(Modift){a,b};
    }
    len = cbrt((double)n * max(1 , mc));
    sort(q+1,q+mq+1,cmp);
    for(int i=0,j=1,t=0,k=1,res=0;k<=mq;k++)
    {
        int id=q[k].id,l=q[k].l,r=q[k].r,tm=q[k].t;
        while(i<r)  add(w[++i],res);
        while(i>r)  del(w[i--],res);
        while(j<l)  del(w[j++],res);
        while(j>l)  add(w[--j],res);
        while(t<tm)
        {
            t++;
            if(c[t].p>=j&&c[t].p<=i)
            {
                del(w[c[t].p],res);
                add(c[t].c,res);
            }
            swap(w[c[t].p],c[t].c);
        }
        while(t>tm)
        {
            if(c[t].p>=j&&c[t].p<=i)
            {
                del(w[c[t].p],res);
                add(c[t].c,res);
            }
            swap(w[c[t].p],c[t].c);
            t--;
        }
        ans[id]=res;
    }
    for(int i=1;i<=mq;i++)  printf("%d\n",ans[i]);
    return 0;
}

回滚莫队

如上所示的莫队都是对于单点修改的比较简洁的莫队,但是有些时候修改可能就不利于操作,比如需要维护状态最值的问题,在区间加入值的时候我们可以直接迭代,但是对于减少元素的时候,我们就难于更改。在排序的时候我们的右端点是递增的,所以我们可以对于同一块内的区间直接暴力,时间是根号n;对于块间我们首先把最左快的进行暴力处理,对于右边的块我们严格递增即可。时间复杂度是n根号n。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int n,m,len;
int w[N],cnt[N];
LL ans[N];
struct Query{
    int id,l,r;
}q[N];
vector<int> nums;
inline int get(int x){return x/len;}
bool cmp(const Query& a,const Query& b)
{
    int i=get(a.l),j=get(b.l);
    if(i!=j)    return i<j;
    return a.r<b.r;
}
void add(int x,LL &res)
{
    cnt[x]++;
    res=max(res,(LL)cnt[x]*nums[x]);
}
int main()
{
    scanf("%d%d",&n,&m);
    len=sqrt(n);
    for(int i=1;i<=n;i++)   scanf("%d",&w[i]),nums.push_back(w[i]);
    sort(nums.begin(),nums.end());
    nums.erase(unique(nums.begin(),nums.end()),nums.end());
    for(int i=1;i<=n;i++)
        w[i]=lower_bound(nums.begin(),nums.end(),w[i])-nums.begin();
    for(int i=0;i<m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        q[i]=(Query){i,l,r};
    }
    sort(q,q+m,cmp);
    for(int x=0;x<m;)
    {
        int y=x;
        while(y<m&&get(q[y].l)==get(q[x].l))    y++;
        int right=get(q[x].l)*len+len-1;
        while(x<y&&q[x].r<right)
        {
            LL res=0;
            int id=q[x].id,l=q[x].l,r=q[x].r;
            for(int k=l;k<=r;k++)   add(w[k],res);
            ans[id]=res;
            for(int k=l;k<=r;k++)   cnt[w[k]]--;
            x++;
        }
        LL res=0;
        int i=right,j=right+1;
        while(x<y)
        {
            int id=q[x].id,l=q[x].l,r=q[x].r;
            while(i<r)  add(w[++i],res);
            LL backup=res;
            while(j>l)  add(w[--j],res);
            ans[id]=res;
            while(j<right+1)    cnt[w[j++]]--;
            res=backup;
            x++;
        }
        memset(cnt,0,sizeof cnt);
    }
    for(int i=0;i<m;i++)    printf("%lld\n",ans[i]);
    return 0;
}

树上莫队

对于树上的路径对于莫队来说一般用的是欧拉序列的应用,欧拉序列是指对树进行dfs遍历,在dfs前输出一次,在回溯后再输出一次。对于LCA可以利用倍增也可以用tarjan,在线或者离线的利用。本题是求树上的路径的不同值个数。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N = 1e5+10;
int w[N];
int n,m,len;
int head[N],idx;
int depth[N],f[N][16];
int seq[N],first[N],last[N],top;
int cnt[N],st[N],ans[N];
int que[N];
struct Edge{
    int next,to;
}edge[N];
struct Query{
    int id,l,r,p;
}q[N];
vector<int> nums;
void add_edge(int a,int b)   {
    edge[idx]=(Edge){head[a],b},head[a]=idx++;
}
void dfs(int u,int father)
{
    seq[++top]=u;
    first[u]=top;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int j=edge[i].to;
        if(j!=father)   dfs(j,u);
    }
    seq[++top]=u;
    last[u]=top;
}
void bfs()
{
    memset(depth,0x3f,sizeof depth);
    depth[0]=0,depth[1]=1;
    int hh=0,tt=0;
    que[0]=1;
    while(hh<=tt)
    {
        int t=que[hh++];
        for(int i=head[t];~i;i=edge[i].next)
        {
            int j=edge[i].to;
            if(depth[j]>depth[t]+1)
            {
                depth[j]=depth[t]+1;
                f[j][0]=t;
                for(int k=1;k<=15;k++)
                    f[j][k]=f[f[j][k-1]][k-1];
                que[++tt]=j;
            }
        }
    }
}
int lca(int a,int b)
{
    if(depth[a]<depth[b])   swap(a,b);
    for(int k=15;k>=0;k--)
        if(depth[f[a][k]]>=depth[b])
            a=f[a][k];
    if(a==b)    return a;
    for(int k=15;k>=0;k--)
        if(f[a][k]!=f[b][k])
        {
            a=f[a][k];
            b=f[b][k];
        }
    return f[a][0];
}
inline int get(int x)  {return x/len;}
bool cmp(const Query& a,const Query& b)
{
    int i=get(a.l),j=get(b.l);
    if(i!=j)    return i<j;
    return  a.r<b.r;
}
void add(int x,int &res)
{
    st[x]^=1;
    if(!st[x])
    {
        cnt[w[x]]--;
        if(!cnt[w[x]]) res--;
    }
    else{
        if(!cnt[w[x]]) res++;
        cnt[w[x]]++;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)   scanf("%d",&w[i]),nums.push_back(w[i]);
    sort(nums.begin(),nums.end());
    nums.erase(unique(nums.begin(),nums.end()),nums.end());
    for(int i=1;i<=n;i++)
        w[i]=lower_bound(nums.begin(),nums.end(),w[i])-nums.begin();
    memset(head,-1,sizeof head);
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add_edge(a,b),add_edge(b,a);
    }
    dfs(1,-1);
    bfs();
    for(int i=0;i<m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        if(first[a]>first[b])   swap(a,b);
        int p=lca(a,b);
        if(a==p)    q[i]={i,first[a],first[b]};
        else    q[i]={i,last[a],first[b],p};
    }
    len=sqrt(top);
    sort(q,q+m,cmp);
    for(int i=0,L=1,R=0,res=0;i<m;i++)
    {
        int id=q[i].id,l=q[i].l,r=q[i].r,p=q[i].p;
        while(R<r)  add(seq[++R],res);
        while(R>r)  add(seq[R--],res);
        while(L<l)  add(seq[L++],res);
        while(L>l)  add(seq[--L],res);
        if(p)   add(p,res);
        ans[id]=res;
        if(p)   add(p,res);
    }
    for(int i=0;i<m;i++)    printf("%d\n",ans[i]);
    return 0;
}


文章作者: Dydong
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Dydong !
  目录