莫队算法、及带修改操作的莫队算法

普通莫队

莫队算法——离线对询问区间进行排序,使之总复杂度从 O ( N 2 ) 降为 O ( N 3 2 )

例题1

利用一道简单的例题:
一串数列,一大堆询问,询问一个区间的不同的数的个数。如a{1,3,2,5,2,3,2}区间[2,4]有3个不同的数。询问次数与数列大小≤100000。

朴素算法及对一个区间暴力遍历,维护基数数组cnt[], O ( n 2 )

考虑另一种暴力,每次扫完一个区间[l,r],向下一个询问[l2,r2]转移,使r±1,l±1,并添加(删除)a[r]和a[l]至cnt[],就可以转移至下一个询问,得到结果。(注意,必须先进行添加操作(r+1或l-1),才能使得不会删除不存在的数)但这样每次移动l和r也是 O ( n ) 级别的,总时间复杂度还是 O ( N 2 )

算法

莫队,将询问经过某种排序,使得转移询问总次数规模变小。

将原序列分为大小为 k n k 个块,并将块从左到右编号;
先将询问的左端点l按照所在块的编号为第一关键字,然后按右端点r升序为第二关键字排序。
这样对于每一个块里的询问,左端点每次最多移动k距离,右端点一共最多移动n距离;
移至下一个块时,左端点最多移动2k距离,右端点最多移动n距离;
又因为块只有 n k 个,
这样左端点做移动距离复杂度为 O ( n k ) ,右端点移动距离复杂度 O ( n n k )
因为总时间复杂度取两者最大值,所以当 k = n 时,总时间复杂度最小为 O ( n 3 2 )

附代码: SPOJ-DQUERY

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=30005,MAXQ=200005,MAXA=1000005;

int n,q,blksiz;
int a[MAXN];

struct query
{
    int l,r,ans,id;
    bool operator < (const query &t)const
    {return (l/blksiz)<(t.l/blksiz)||((l/blksiz)==(t.l/blksiz)&&r<t.r);}
}que[MAXQ];

bool cmp(query a,query b)
{return a.id<b.id;}
int cnt[MAXA];

int main()
{
    scanf("%d",&n);
    blksiz=sqrt(n);
    for(int i=1;i<=n;i++)
        scanf("%d",a+i);
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d",&que[i].l,&que[i].r);
        que[i].id=i;
    }
    sort(que+1,que+q+1);
    int l=1,r=1,ans=1;
    cnt[a[1]]=1;
    for(int i=1;i<=q;i++)
    {
        while(r<que[i].r)
            r++,cnt[a[r]]++,ans+=(cnt[a[r]]==1);
        while(l<que[i].l)
            cnt[a[l]]--,ans-=(cnt[a[l]]==0),l++;
        while(r>que[i].r)
            cnt[a[r]]--,ans-=(cnt[a[r]]==0),r--;
        while(l>que[i].l)
            l--,cnt[a[l]]++,ans+=(cnt[a[l]]==1);
        que[i].ans=ans;
    }
    sort(que+1,que+q+1,cmp);
    for(int i=1;i<=q;i++)
        printf("%d\n",que[i].ans);
    return 0;
}

带修改操作的莫队

例题2

同样是上一道例题,添加修改操作:
一串数列,一大堆操作,可以单点修改一个数,询问一个区间的不同的数的个数。如a{1,3,2,5,2,3,2}区间[2,4]有3个不同的数。操作次数与数列大小≤100000。

算法

每进行一次修改,数组就更新一个版本,给每一个询问添加一个版本号;
转移时,先进行修改操作,将版本号调整至询问版本,然后移动l和r。

需要将询问根据l,r,版本t,按某种顺序进行排序,使得时间复杂度降低。
仍然利用分块,将原序列分为大小为 k n k 个块,并将块从左到右编号;
将l和r都按照块的编号,版本号升序排序;

当l和r都在自己的块内,t最多移动n距离,l和r的块总共有 n k 2 种,t的移动距离复杂度为 O ( n n k 2 )
当l和r都在自己的块内,r每次最多移动 k 次,每一次l更换自己的块时(一共更换 n k 次),r最多移动 n 距离,所以r移动距离复杂度为 O ( n k )
同理,l的移动距离复杂度也为 O ( n k )

为使总时间复杂度最小,令 k = n 2 3 ,总时间复杂度为 O ( n 5 3 ) 。\

附代码:UVA12345

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=50005,MAXA=1000005;

struct query
{int l,r,ti,id;};

int N,OP,blksiz,q,m;
int a[MAXN],mod[MAXN][3];

int ans[MAXN];
query que[MAXN];

bool CaptainMo(query a,query b)
{
    a.l/=blksiz;a.r/=blksiz;
    b.l/=blksiz;b.r/=blksiz;
    if(a.l==b.l)
        return a.r<b.r||(a.r==b.r&&a.ti<b.ti);
    return a.l<b.l;
}

int l,r,t,tans;
int cnt[MAXA];
void add(int x)
{
    cnt[x]++;
    tans+=(cnt[x]==1);
}
void del(int x)
{
    cnt[x]--;
    tans-=(cnt[x]==0);
}
void go()
{
    t++;
    if(l<=mod[t][0]&&mod[t][0]<=r)
    {
        del(mod[t][2]);
        add(mod[t][1]);
    }
    a[mod[t][0]]=mod[t][1];
}
void back()
{
    if(l<=mod[t][0]&&mod[t][0]<=r)
    {
        del(mod[t][1]);
        add(mod[t][2]);
    }
    a[mod[t][0]]=mod[t][2];
    t--;
}
int main()
{
    scanf("%d%d",&N,&OP);
    blksiz=pow(N,2.0/3.0);
    for(int i=1;i<=N;i++)
        scanf("%d",a+i);
    for(int i=1;i<=OP;i++)
    {
        char op[5];
        scanf("%s",op);
        if(op[0]=='M')
            m++,scanf("%d%d",mod[m],mod[m]+1),mod[m][0]++;
        else
            q++,scanf("%d%d",&que[q].l,&que[q].r),que[q].ti=m,que[q].id=q,que[q].l++;
    }
    for(int i=1;i<=m;i++)
        mod[i][2]=a[mod[i][0]],a[mod[i][0]]=mod[i][1];
    for(int i=m;i>0;i--)
        a[mod[i][0]]=mod[i][2];
    sort(que+1,que+q+1,CaptainMo);
    l=1,r=1,t=0,tans=1;
    cnt[a[1]]=1;
    for(int i=1;i<=q;i++)
    {
        while(t<que[i].ti)
            go();
        while(t>que[i].ti)
            back();
        while(l<que[i].l)
            del(a[l]),l++;
        while(l>que[i].l)
            l--,add(a[l]);
        while(r<que[i].r)
            r++,add(a[r]);
        while(r>que[i].r)
            del(a[r]),r--;
        ans[que[i].id]=tans;
    }
    for(int i=1;i<=q;i++)
        printf("%d\n",ans[i]);
    return 0;
}

推广

带修改操作的莫队可看做三维询问的莫队;
同理可以做出四维,五维等k维莫队,时间复杂度为 O ( n k + 1 k )

猜你喜欢

转载自blog.csdn.net/can919/article/details/79393546