莫队算法笔记

莫队算法用于解决不带修改的离线区间问题。
其主要思想是这样的:
先已知区间[L,R]的答案为ans,如果求出区间[L+1,R],[L-1,R],[L,R-1],[L,R+1]的时间均为alpha(),那么就可以在alpha()的时间内转移一个单位区间,求出答案ans’。
因此,我们可以通过安排查询的顺序来使得转移次数尽量少。
可以用平面最小曼哈顿距离生成树来确定查询顺序,不过有一个更好写的方法是分块。
把区间分成sqrt(n)块,对于查询[l,r]和[l’,r’],如果l和l’在一个块内,那么就把r小的放前面。如果不在一个块内,那么把l小的前面。时间复杂度O(nsqrt(n))
然后查询的时候改变l和r,同时改变答案并保存。
最后按顺序输出答案。
贴一个NBUT 1457 Sona的莫队算法模板(求区间中每个数出现次数的立方和)

#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
int pos[100010],col[100010],a[100010],cnt[100010],n,m,block;
ll cubesum;
struct node{
    int l,r,id;
    ll ans;
}data[100010];
bool cmp1(node a,node b)
{
    if(pos[a.l]==pos[b.l])return a.r<b.r;
    return a.l<b.l;
}
bool cmp2(node a,node b)
{
    return a.id<b.id;
}
void update(int pos,int val)
{
    cubesum-=1ll*cnt[col[pos]]*cnt[col[pos]]*cnt[col[pos]];
    cnt[col[pos]]+=val;
    cubesum+=1ll*cnt[col[pos]]*cnt[col[pos]]*cnt[col[pos]];
}
int main()
{
    while(~scanf("%d",&n))
    {
        cubesum=0;
        memset(cnt,0,sizeof cnt);
        block=sqrt(n);
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]),col[i]=a[i],pos[i]=(i-1)/block;
        sort(a+1,a+n+1);
        int len=unique(a+1,a+n+1)-a-1;
        for(int i=1;i<=n;++i)
            col[i]=lower_bound(a+1,a+len+1,col[i])-a;
        scanf("%d",&m);
        for(int i=1;i<=m;++i)
        {
            scanf("%d%d",&data[i].l,&data[i].r);
            data[i].id=i;
        }
        sort(data+1,data+m+1,cmp1);
        for(int i=1,l=1,r=0;i<=m;++i)
        {
            for(;r<data[i].r;++r)update(r+1,1);
            for(;r>data[i].r;--r)update(r,-1);
            for(;l<data[i].l;++l)update(l,-1);
            for(;l>data[i].l;--l)update(l-1,1);
            data[i].ans=cubesum;
        }
        sort(data+1,data+m+1,cmp2);
        for(int i=1;i<=m;++i)
            printf("%I64d\n",data[i].ans);
    }
}

接下来是树上莫队。这个是解决无修改离线树上路径的问题。
要思考怎么才能把树上离散的区间变成连续的。
常见的做法是dfs序。
即进u点记录一个时间戳为l,出u点也记录一个时间戳为r,
加入u比v先访问,u不是lca(u,v),则[u,v]就是r[u],l[v]。
如果u是lca(u,v),那么u,v在链上,则[u,v]就是l[u],l[v]。
然后还要注意在数列里面l,r是直接加减1的。
但是在树上不能这么做。
记s(u,v)为树上u到v路径上点的集合,那么s(u,v)=s(u,root) xor s(v,root) xor lca(u,v)
记t(u,v)=s(u,root) xor s(v,root)
则t(u,v’)=s(u,root) xor s(v’,root)
因此t(u,v) xor t(u,v’)=s(v,root) xor s(v’,root)=t(v,v’)
这样说明把区间[u,v]移到区间[u,v’]的时候应该对[v,v’]上除了lca(v,v’)外的所有点取反。
贴一个SPOJ COT2

#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
struct node{
    int v,next;
}edge[80010];
struct query{
    int l,r,ans,id;
}data[100010];
int sz[40010],top[40010],son[40010],a[40010],col[40010],sum[40010],l[40010],r[40010],dfn[40010],dep[40010],fa[40010],head[40010],pos[80010],idx[80010],block,cnt,dcnt,tot,now,n,m,len;
bool vis[40010];
bool cmp1(query a,query b)
{
    if(pos[a.l]==pos[b.l])return a.r<b.r;
    return a.l<b.l;
}
bool cmp2(query a,query b)
{
    return a.id<b.id;
}
void addedge(int u,int v)
{
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
void dfs1(int u,int f)
{
    sz[u]=1;
    int maxch=0;
    dfn[u]=++dcnt;
    l[u]=++tot;
    idx[tot]=u;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v!=f)
        {
            fa[v]=u;
            dep[v]=dep[u]+1;
            dfs1(v,u);
            sz[u]+=sz[v];
            if(sz[v]>maxch)
            {
                maxch=sz[v];
                son[u]=v;
            }
        }
    }
    r[u]=++tot;
    idx[tot]=u;
}
void dfs2(int u,int t)
{
    top[u]=t;
    if(son[u]==0)return;
    dfs2(son[u],t);
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v!=son[u]&&v!=fa[u])dfs2(v,v);
    }
}
int lca(int u,int v)
{
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        u=fa[top[u]];
    }
    return dep[u]<dep[v]?u:v;
}
void update(int u)
{
    if(vis[u])now-=((--sum[col[u]])==0);
    else now+=((++sum[col[u]])==1);
    vis[u]^=1;
}
int main()
{
    memset(head,-1,sizeof head);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]),col[i]=a[i];
    sort(a+1,a+n+1);
    len=unique(a+1,a+n+1)-a-1;
    for(int i=1;i<=n;++i)
        col[i]=lower_bound(a+1,a+len+1,col[i])-a;
    for(int i=1,u,v;i<n;++i)
    {
        scanf("%d%d",&u,&v);
        addedge(u,v);
        addedge(v,u);
    }
    fa[1]=0;
    dep[1]=1;
    dfs1(1,0);
    dfs2(1,1);
    block=sqrt(n);
    for(int i=1;i<=tot;++i)pos[i]=(i-1)/block;
    for(int i=1,x,y;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        if(dfn[x]>dfn[y])swap(x,y);
        int t=lca(x,y);
        if(t==x)data[i].l=l[x],data[i].r=l[y];
        else data[i].l=r[x],data[i].r=l[y];
        data[i].id=i;
    }
    sort(data+1,data+m+1,cmp1);
    for(int i=1,l=1,r=0;i<=m;++i)
    {
        while(l<data[i].l)update(idx[l++]);
        while(l>data[i].l)update(idx[--l]);
        while(r<data[i].r)update(idx[++r]);
        while(r>data[i].r)update(idx[r--]);
        int u=idx[l],v=idx[r],t=lca(u,v);
        if(t!=u&&t!=v)update(t);
        data[i].ans=now;
        if(t!=u&&t!=v)update(t);
    }
    sort(data+1,data+m+1,cmp2);
    for(int i=1;i<=m;++i)
        printf("%d\n",data[i].ans);
}

最后再来一个带修改莫队,时间复杂度O(n^(5/3))
待修改的那么就把时间看成第三维,然后就变成了一个空间曼哈顿最小距离生成树,这个怎么求?= =
其实好像没人研究吧。
不过还是可以分块来做的。
每个块的大小是n^(2/3),分成n^(1/3)块
然后排序莫队。
注意是每次询问操作时间+1,每次修改操作不加时间。
这个代码是给定一个N个正整数的序列S,编号0~N-1。给出M个操作,每个操作可以是以下两种之一:
(1)Q x y: 询问区间S[x]..S[y-1]中有多少种整数,相同的整数算一种
(2)M x y:表示 S[x] = y
正解是带修改可持久化线段树,要先离散化,再转换一下数组,转化成前一个数出现的位置。O(log^2n)
或者直接分块(不是莫队),保存前一个数出现的位置。O(nsqrt(n))
莫队能得90分,最后一个T了。
1s有点短。。1.5s可以AC。

#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
int pos[50010],his[50010],ans[50010],col[50010],cnt[1000010],n,m,block,szq,szm,sum;
char op[3],c;
bool vis[1000010];
struct node{
    int l,r,id,t;//in query l,r means [L,R],t means time,id=t is used to find where the answer is.
}query[50010],modification[50010];//in modification l means from,r means to,id means where will be modified,t means time
inline void GET(int &n)
{
    n=0;
    do{c=getchar();}while(c >'9'||c<'0');
    while(c>='0'&&c<='9'){n=n*10+c-'0';c=getchar();}
}
inline bool cmp1(node a,node b)
{
    if(pos[a.l]==pos[b.l])
    {
        if(pos[a.r]==pos[b.r])
            return a.t<b.t;
        return a.r<b.r;
    }
    return a.l<b.l;
}
inline void update(int u)
{
    if(vis[u])sum-=((--cnt[col[u]])==0);
    else sum+=((++cnt[col[u]])==1);
    vis[u]^=1;
}
inline void change(node x,bool f)
{
    if(f)swap(x.l,x.r);
    col[x.id]=x.r;
    if(vis[x.id])
    {
        sum-=((--cnt[x.l])==0);
        sum+=((++cnt[x.r])==1);
    }
}
int main()
{
    GET(n),GET(m);
    block=pow(n,2.11/3);
    for(int i=1;i<=n;++i)
        GET(col[i]),pos[i]=(i-1)/block;
    for(int i=1,x,y;i<=m;++i)
    {
        scanf("%s",op);
        GET(x),GET(y);
        ++x;
        if(op[0]=='M')
        {
            modification[++szm].id=x;
            modification[szm].l=col[x];
            modification[szm].r=col[x]=y;
            modification[szm].t=szq;
        }
        else
        {
            query[++szq].l=x;
            query[szq].r=y;
            query[szq].t=szq;
        }
    }
    sort(query+1,query+szq+1,cmp1);
    for(int i=1,l=1,r=0,t=szm;i<=szq;++i)
    {
        for(;r<query[i].r;++r)update(r+1);
        for(;r>query[i].r;--r)update(r);
        for(;l<query[i].l;++l)update(l);
        for(;l>query[i].l;--l)update(l-1);
        for(;t>0&&modification[t].t>=query[i].t;--t)
            change(modification[t],1);
        for(;t<szm&&modification[t+1].t<query[i].t;++t)
            change(modification[t+1],0);
        ans[query[i].t]=sum;
    }
    for(int i=1;i<=szq;++i)
        printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值