UOJ Logo zgjkt的博客

博客

各种莫队算法

2016-12-26 14:10:07 By zgjkt

[bzoj2648]小Z的袜子——普通莫队

题目大意

给出一个有n个数的数列A:a1,a2,a3an,询问m

每次询问给出一个区间[l,r],询问区间[l,r]内抽到一对数值相等的数的概率大小

数据范围

n,m500001l<rnain


题解

不妨设区间[l,r]里元素个数为len,显然,抽一对数的情况总数为Clen2

同时区间里只有k种数值,每种数值对应的元素有s1,s2sk个,抽一对数值相等的数的情况总数为Cs12+Cs22+Csk2

则答案为Cs12+Cs22+Csk2Clen2=s1(s11)+s2(s21)+sk(sk1)len(len1)=s12+s22+sk2lenlen(len1)

那么现在问题简化为,如何即时地统计所有的s1,s2sk

这个时候引入一个东西,莫队算法,是莫涛前几年提出的算法,适用于解决离线区间查询问题

Question Ⅰ:这个算法怎么用啊?

假设已经得到了区间[l,r]需要统计的信息,可以在常数级别的时间内转移到区间[l1,r]、区间[l+1,r]、区间[l,r1]、区间[l,r+1]

那么可以在O(nn)的时间复杂度求得所有答案

Question Ⅱ:这个时间复杂度?

我们可以调整一下处理每个查询的顺序,保证最优的顺序之下转移区间

n个数分成n

按区间排序,以左端点所在块内为第一关键字,右端点为第二关键字,进行排序

然后按这个排序直接暴力,复杂度分析是这样的:

  1. ii+1在同一块内,r单调递增,所以rO(n)的,由于有n块,所以这一部分时间复杂度是nn

  2. ii+1跨越一块,r最多变化n,由于有n块,所以这一部分时间复杂度是nn

  3. ii+1在同一块内,l变化不超过n,跨越一块也不会超过n×2

由于有m次询问(和n同级),所以时间复杂度是nn


代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cctype>
#include<algorithm>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define Ford(i,r,l) for(int i=r;i>=l;--i)
inline int read(){
    int x=0;char ch=getchar();
    while (!isdigit(ch)) ch=getchar();
    while (isdigit(ch)) {x=x*10+ch-48;ch=getchar();}
    return x;
}
using namespace std;

int n,m,size;
struct data{int l,r,id;long long up,down;}q[50050];
int a[50050],belong[50050]; 
ll ans,s[50050];
inline bool cmp1(data x,data y) {return belong[x.l]==belong[y.l]?x.r<y.r:x.l<y.l;}
inline bool cmp2(data x,data y) {return x.id<y.id;}
inline ll gcd(ll x,ll y){return y==0?x:gcd(y,x%y);}

inline void update(int x,int c){
    ans-=s[x]*s[x];
    s[x]+=(ll)c;
    ans+=s[x]*s[x];
}

int main(){
    n=read(),m=read();
    For(i,1,n) a[i]=read();
    For(i,1,m) q[i]=(data){read(),read(),i};

    size=(int)sqrt(n);
    For(i,1,n) belong[i]=(i-1)/size+1;
    sort(q+1,q+m+1,cmp1);

    int l=1,r=0;
    For(i,1,m){
        for(;r<q[i].r;r++) update(a[r+1],1);
        for(;r>q[i].r;r--) update(a[r],-1);
        for(;l<q[i].l;l++) update(a[l],-1);
        for(;l>q[i].l;l--) update(a[l-1],1);
        if (q[i].l==q[i].r){
            q[i].up=0;q[i].down=1;
            continue;
        }
        ll len=q[i].r-q[i].l+1;
        q[i].up=ans-len,q[i].down=(len-1)*len;
        ll g=gcd(q[i].up,q[i].down);
        q[i].up/=g,q[i].down/=g;
    }
    sort(q+1,q+m+1,cmp2);
    For(i,1,m) printf("%lld/%lld\n",q[i].up,q[i].down);
    return 0;
}


[bzoj2120]数颜色——带修改莫队

题目大意

n支彩色画笔(颜色可能相同)排成一排,有m次操作

  1. 询问在[l,r]中共有几种颜色不同的画笔

  2. 把其中一只画笔替换为别的颜色的画笔

    数据范围

    n10000,m10000

所有画笔的颜色表示为1c[i]106


题解

以前做这道题的时候想过用分块去做,具体戳分块题解

其实这道题除了分块,也可以想想莫队的思路

思考熊

Question Ⅲ:查询操作的区间还是要调整成最优区间,那么修改操作的顺序就会被打乱,怎么解决?

对于每一个查询操作,我们记录在它之前最近的修改操作的编号,维护两个时间段之间的修改操作

就是对于现在的这个查询,在它之后执行过的修改操作改回去,在它之前没执行的修改就执行

Question Ⅳ:这个时间复杂度?

排序方法:设定块的长度为S1S2,按照(lS1,rS2,t)的三元组小到大排序,其中t表示这个询问的时刻之前经历过了几次修改操作

复杂度分析:考虑询问序列中的每个小块,小块内每个询问的一二关键字相同

在这个小块内,显然t最多变化m,对于每个询问,l,r最多变化S1S2,一共有n2S1S2个这样的块,相邻块之间转移的复杂度为O(n),总复杂度就是O(mS1+mS2+n2mS1S2+n2mS1S2)

不妨设n,m同阶,取S1=S2=n23时可达到最优复杂度O(n53)


程序

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cctype>
#include<algorithm>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define Ford(i,r,l) for(int i=r;i>=l;--i)
inline int read(){
    int x=0;char ch=getchar();
    while (!isdigit(ch)) ch=getchar();
    while (isdigit(ch)) {x=x*10+ch-48;ch=getchar();}
    return x;
}
using namespace std;

int n,m,cnt_r,cnt_q;
struct revise{int pos,col,pre;}c[1050];
struct query{int l,r,id,time;}q[10050];
int belong[10050],last[10050];
int colour[1000500],num[1000500],vis[10050];
int ans[10050],sum;
inline bool cmp(query a,query b){return belong[a.l]==belong[b.l]?a.r<b.r:belong[a.l]<belong[b.l];}

inline void cal(int x){
    if (vis[x]){
        num[colour[x]]--;
        if (num[colour[x]]==0) sum--;
    }
    else{
        if (num[colour[x]]==0) sum++;
        num[colour[x]]++;
    }
    vis[x]^=1;
}
inline void change(int x,int c){
    if (vis[x]){
        cal(x);
        colour[x]=c;
        cal(x);
    }
    else colour[x]=c;
}

int main(){
    n=read(),m=read();
    For(i,1,n) colour[i]=last[i]=read();
    For(i,1,m){
        char ch[5];scanf("%s",ch);
        int x=read(),y=read();
        if (ch[0]=='R') c[++cnt_r]=(revise){x,y,last[x]},last[x]=y;
        else q[++cnt_q]=(query){x,y,cnt_q,cnt_r};
    }

    int size=ceil(pow(cnt_q,2.0/3));
    For(i,1,cnt_q) belong[i]=(i-1)/size+1;
    sort(q+1,q+cnt_q+1,cmp);
    int l=1,r=0;
    For(i,1,cnt_q){
        for (int j=q[i-1].time+1;j<=q[i].time;j++) change(c[j].pos,c[j].col);
        for (int j=q[i-1].time;j>=q[i].time+1;j--) change(c[j].pos,c[j].pre);
        for (;r<q[i].r;r++) cal(r+1);
        for (;r>q[i].r;r--) cal(r);
        for (;l<q[i].l;l++) cal(l);
        for (;l>q[i].l;l--) cal(l-1);
        ans[q[i].id]=sum;
    }

    For(i,1,cnt_q) printf("%d\n",ans[i]);
}


[bzoj3757]苹果树——树上无修改莫队

题目大意

有一棵有n个节点的树,执行m次询问,每个点有一种颜色

每次询问uv路径上把颜色a和颜色b当作同一种颜色后路径上不同颜色的数目

数据范围

n5×104,m105


题解

参考:https://blog.sengxian.com/algorithms/mo-s-algorithm


程序

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cctype>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define goedge(i,x) for(int i=last[x];i;i=e[i].next)
#define N 50500
using namespace std;
inline int read(){
    int x=0;char ch=getchar();
    while (!isdigit(ch)) ch=getchar();
    while (isdigit(ch)) {x=x*10+ch-48;ch=getchar();}
    return x;
}

int n,m,block,root,et,cnt,tot,top,ans;
struct edge{int to,next;}e[2*N];
struct data{int u,v,a,b,id;}q[2*N];
int last[N],bin[25];
int dfn[N],deep[N],belong[N],st[N],fa[N][25];
int c[N],sum[N],vis[N],result[2*N];

inline bool cmp(data x,data y){
    return belong[x.u]==belong[y.u]?dfn[x.v]<dfn[y.v]:belong[x.u]<belong[y.u];
}
inline void addedge(int u,int v){
    e[++et]=(edge){v,last[u]};last[u]=et;
    e[++et]=(edge){u,last[v]};last[v]=et;
}
inline int lca(int u,int v){
    if (deep[u]<deep[v]) swap(u,v);
    int t=deep[u]-deep[v];
    for(int i=0;bin[i]<=t;i++)
        if (t&bin[i]) u=fa[u][i];
    for(int i=16;i>=0;i--)
        if (fa[u][i]!=fa[v][i])
            u=fa[u][i],v=fa[v][i];
    if (u==v) return u;
    return fa[u][0];
}

int dfs(int x){
    int size=0;
    dfn[x]=++cnt;
    For(i,1,16){
        if (deep[x]>=bin[i]) 
            fa[x][i]=fa[fa[x][i-1]][i-1];
        else break;
    }
    goedge(i,x){
        if (e[i].to==fa[x][0]) continue;
        deep[e[i].to]=deep[x]+1;
        fa[e[i].to][0]=x;
        size+=dfs(e[i].to);
        if (size>block){
            tot++;
            For(i,1,size) belong[st[top--]]=tot;
            size=0;
        }
    }
    st[++top]=x;
    return size+1;
}

inline void reverse(int x){
    if (!vis[x]) {vis[x]=1;sum[c[x]]++;if (sum[c[x]]==1) ans++;}
    else {vis[x]=0;sum[c[x]]--;if (sum[c[x]]==0) ans--;}
}

inline void solve(int u,int v){
    while (u!=v){
        if (deep[u]>deep[v]) reverse(u),u=fa[u][0];
        else reverse(v),v=fa[v][0];
    }
}

int main(){
    bin[0]=1;For(i,1,20) bin[i]=bin[i-1]<<1;
    n=read(),m=read();
    For(i,1,n) c[i]=read();
    For(i,1,n){
        int u=read(),v=read();
        if (!u) root=v;
        else if (!v) root=u;
        else addedge(u,v);
    }

    block=sqrt(n);
    dfs(root);
    tot++;
    while (top) belong[st[top--]]=tot;

    For(i,1,m){
        q[i]=(data){read(),read(),read(),read(),i};
        if (dfn[q[i].u]>dfn[q[i].v]) swap(q[i].u,q[i].v);
    }
    sort(q+1,q+m+1,cmp);

    int h=lca(q[1].u,q[1].v);
    solve(q[1].u,q[1].v);
    reverse(h);
    result[q[1].id]=ans;
    if (sum[q[1].a] && sum[q[1].b] && q[1].a!=q[1].b) 
        result[q[1].id]--;
    reverse(h);

    For(i,2,m){
        solve(q[i-1].u,q[i].u);
        solve(q[i-1].v,q[i].v);
        int h=lca(q[i].u,q[i].v);
        reverse(h);
        result[q[i].id]=ans;
        if (sum[q[i].a] && sum[q[i].b] && q[i].a!=q[i].b)
            result[q[i].id]--;
        reverse(h);
    }

    For(i,1,m) printf("%d\n",result[i]);
    return 0;
}


评论

资辞一发

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。