莫队算法

莫队算法

作用

一个超级强大的暴力,可以处理一大堆区间问题,效率也不低。美中不足的是这是个离线算法(感觉很多强大的算法都是离线……)。

实现

有些区间问题可以在知道[L,R]的情况下用很短的时间(比如 O(1) O(log2(n)) )求出[L-1,R],[L+1,R],[L,R-1],[L,R+1],这样的话莫队算法就有很大的威力了。

比如求[L,R]中不同颜色的个数,一旦颜色数很大,线段树就不行了。而求不同颜色个数能在 O(1) 内完成递推,所以可以这样做:
1.求出[L,R]。
2.不停求[L-1,R],[L+1,R],[L,R-1],[L,R+1]直到求出下一个问题[L ,R ]。

怎么样,很暴力吧……但是显然如果出现[1,1]和[n,n]交替的数据,n一大就可以卡死上述做法。所以莫队算法将对上述做法进行优化:分块。

将Q个询问按照L分块,分为[1, n ],( n , 2n ]……然后按R从小到大排序每个块,然后用上述做法处理即可。但复杂度如何保证?证明如下:
计算次数=L移动次数+R移动次数
L移动次数:
1.相同块中,i到i+1最多移动 n 次,总共Q个询问,移动次数为Q* n
2.不同块中,i到i+1最多移动 n 次,总共 n 个块,移动次数为n。
R移动次数:
1.相同块中,总计移动最多n次,总共 n 个块,移动次数为n* n
2.不同块中,i到i+1最多移动n次,总共 n 个块,移动次数为n* n

所以莫队算法的理论复杂度为 O((n+Q)n) ,实际处理起来会小很多,特别是随机数据。

模板

以BZOJ1878为例。
就是上面说的问题,不解释了。

#include
#include
#include
using namespace std;
const int maxn=50000,maxm=200000,maxc=1000000;

int n,m,col[maxn+5];
int ans[maxm+5],sum,ha[maxc+5];
struct Ask
{
    int L,R,pa,id;
    bool operator < (const Ask &a) const {return paq[maxm+5];

bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x)
{
    int tot=0,f=1;char ch=getchar(),lst='+';
    while ('9''0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
    if (lst=='-') f=-f;
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
    x=tot*f;
    return Eoln(ch);
}
void Update(int pos,int tem) //更新
{
    if (!ha[col[pos]]) sum++;
    ha[col[pos]]+=tem;
    if (!ha[col[pos]]) sum--;
}
int main()
{
    freopen("MoTao.in","r",stdin);
    freopen("MoTao.out","w",stdout);
    readi(n);
    for (int i=1;i<=n;i++) readi(col[i]);
    readi(m);
    for (int i=1,sq=sqrt(n);i<=m;i++)
    {
        readi(q[i].L);readi(q[i].R);
        q[i].pa=(q[i].L-1)/sq+1;q[i].id=i;
    }
    sort(q+1,q+1+m);
    sum=0;for (int i=q[1].L;i<=q[1].R;i++) if (!(ha[col[i]]++)) sum++;
    ans[q[1].id]=sum;
    for (int i=2;i<=m;i++)
    {
        int L=q[i-1].L,R=q[i-1].R;
        while (L<q[i].L) Update(L++,-1);
        while (L>q[i].L) Update(--L,1);
        while (R<q[i].R) Update(++R,1);
        while (R>q[i].R) Update(R--,-1);
        //用上一状态求当前状态
        ans[q[i].id]=sum;
    }
    for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}

有单点修改的莫队算法

作用

可以解决有单点修改的区间问题。

实现

其实想法很简单,就是加一个时间ti,然后排序时按照L所在块,R所在块,ti大小进行排序。每次除了要移动L和R之外,还要移动ti。不过效率好像很神奇,貌似取决于分块的块大小。网上的大神们块大小都取 n23 ,这样时间复杂度就为 O((n+Q)n23) 了,证明如下:
计算次数=L移动次数+R移动次数+ti移动次数
L移动次数:
1.相同L块中,i到i+1最多移动 n23 次,总共Q个询问,移动次数为Q* n23
2.不同L块中,i到i+1最多移动 n23 次,总共 n13 个L块,移动次数为n。
R移动次数:
1.相同R块中,i到i+1最多移动 n23 次,总共Q个询问,移动次数为Q* n23
2.不同R块中,i到i+1最多移动 n23 次,总共 n23 个R块,移动次数为 n43
3.不同L块中,i到i+1最多移动n次,总共 n13 个L块,移动次数为 n43
ti移动次数:
1.相同R块中,总计移动最多n次,总共 n23 个R块,移动次数为 n53
2.不同R块中,i到i+1最多移动n次,总共 n23 个R块,移动次数为 n53
3.不同L块中,i到i+1最多移动n次,总共 n13 个L块,移动次数为 n43

综上所述,当块大小为 n23 时,理论复杂度为 O((n+Q)n23) 。虽然感觉和证明里一连串式子比起来偏差有点大,不过同普通莫对一样,实际处理起来比理论复杂度小很多,不需要太过担心。

模板

以BZOJ2120为例。
就是上面说的问题,不解释了。

#include
#include
#include
using namespace std;
const int maxn=10000,maxm=10000,maxc=1000000;

int n,m,mQ,mM,col[maxn+5],lst[maxn+5];
int ans[maxm+5],sum,ha[maxc+5];
struct Query
{
    int L,R,ti,id,paL,paR;
    bool operator < (const Query &a) const
    {
        if (paLreturn true;
        if (paL==a.paL&&paRreturn true;
        if (paL==a.paL&&paR==a.paR&&tireturn true;
        return false;
    }
};
Query Q[maxm+5];
struct Modify
{
    int pos,col,lst;
};
Modify M[maxm+5];

bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x)
{
    int tot=0,f=1;char ch=getchar(),lst='+';
    while ('9''0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
    if (lst=='-') f=-f;
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
    x=tot*f;
    return Eoln(ch);
}
char getrch() {char ch=getchar();while (ch!='Q'&&ch!='R') ch=getchar();return ch;}
void Update(int pos,int tem)
{
    if (!ha[col[pos]]) sum++;
    ha[col[pos]]+=tem;
    if (!ha[col[pos]]) sum--;
}
void Change(int pos,int now,int L,int R) //将pos修改为now
{
    if (L<=pos&&pos<=R) Update(pos,-1),col[pos]=now,Update(pos,1); else
    col[pos]=now;
}
int main()
{
    freopen("MoTao.in","r",stdin);
    freopen("MoTao.out","w",stdout);
    readi(n);readi(m);
    for (int i=1;i<=n;i++) readi(col[i]),lst[i]=col[i];
    for (int i=1,si=pow(n,(double)2/3);i<=m;i++)
    {
        char ch=getrch();int x,y;readi(x);readi(y);
        if (ch=='Q')
        {
            Q[++mQ].L=x;Q[mQ].R=y;Q[mQ].ti=mM;Q[mQ].id=mQ;
            Q[mQ].paL=(x-1)/si+1;Q[mQ].paR=(y-1)/si+1;
        } else M[++mM].pos=x,M[mM].col=y,M[mM].lst=lst[x],lst[x]=y;
        //因为当前x的颜色在改成y之前可能就被改过了,所以要记录lst
    }
    sort(Q+1,Q+1+mQ);
    int L=1,R=1,ti=0;Update(1,1);
    for (int i=1;i<=mQ;i++)
    {
        while (tiwhile (ti>Q[i].ti) Change(M[ti].pos,M[ti].lst,L,R),ti--;
        while (L1);
        while (L>Q[i].L) Update(--L,1);
        while (R1);
        while (R>Q[i].R) Update(R--,-1);
        ans[Q[i].id]=sum;
    }
    for (int i=1;i<=mQ;i++) printf("%d\n",ans[i]);
    return 0;
}

你可能感兴趣的:(离线,莫队算法,算法&数据结构总结By_ZZK)