莫队算法

莫队在知乎回答了一波,推荐了一篇博客,但原网址挂了。最后凭借搜狗的网页快照,我终于一睹这篇博客的真容。

例题 (2010年国家集训队论文答辩)小K的袜子

对于询问([L,R]),设其中颜色为(x,y,z...)的袜子的个数为(a,b,c...)

那么答案即为(cfrac{cfrac{a(a-1)}{2}+cfrac{b(b-1)}{2}+cfrac{c(c-1)}{2}+...}{cfrac{(R-L+1)(R-L)}{2}})

化简得:(cfrac{a^2+b^2+c^2+...x^2-(a+b+c+d+.....)}{(R-L+1)(R-L)})

即:(cfrac{a^2+b^2+c^2+...x^2-(R-L+1)}{(R-L+1)(R-L)})

所以这道题目的关键是求一个区间内每种颜色数目的平方和。

对于一般区间维护类问题,一般用线段树,但是这题完全不知道线段树怎么做,搜索后得知是莫队算法。

莫队算法可以一个可高效解决绝大多数离线+无修改+区间查询问题的算法。这类问题具体是指:如果知道([L,R])的答案时,可以在(O(mathrm{g}(n)))的时间下得到([L,R-1],)([L,R+1],)([L-1,R],)([L+1,R])的答案的话,就可以(O(nsqrt n · mathrm{g}(n)))的时间内求出所有查询。

对于莫队算法我感觉就是暴力。由于预先知道所有的询问,因此可以合理的组织计算每个询问的顺序以此来降低复杂度。

假设我们算完([L,R])的答案后现在要算([L',R'])的答案。由于可以在(O(1))的时间下得到([L,R-1],)([L,R+1],)([L-1,R],)([L+1,R])的答案,所以计算([L',R'])的答案耗时(|L-L'|+|R-R'|)。如果把询问([L,R])看做平面上的点(a(L,R)),询问([L',R'])看做点(b(L',R'))的话,那么时间开销就为两点的曼哈顿距离。

因此,如果将每个询问看做平面上的一个点,按一定顺序计算每个值,那开销就为曼哈顿距离的和。要计算到每个点,路径至少会形成一棵树。所以问题就变成了求二维平面的最小曼哈顿距离生成树

这样只要顺着树边计算一次就OK了,可以证明时间复杂度为(O(nsqrt{n}))(这个我不会证明),但是这种方法的编程复杂度稍微高了一点。

有一个比较优雅的替代品:先对序列分块,然后对于所有询问按照(L)所在块的大小排序,如果一样再按照(R)排序,最后再计算。为什么这样计算就可以降低复杂度呢?

一、(i)(i+1)在同一块内,(r)单调递增,所以(r)(O(n))的。由于有(sqrt{n})块,所以这一部分时间复杂度是(O(nsqrt{n}))
二、(i)(i+1)跨越一块,(r)最多变化(n),由于有(sqrt{n})块,所以这一部分时间复杂度是(O(nsqrt{n}))
三、(i)(i+1)在同一块内时,变化不超过(sqrt{n}),跨越一块也不会超过(sqrt{n}),不妨看作是(sqrt{n})。由于有n个数,所以时间复杂度是(O(nsqrt{n}))
于是就变成了(O(nsqrt{n}))了。

详细过程见代码:

#include<algorithm>
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<math.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=50010;
typedef long long ll;
ll num[maxn],up[maxn],dw[maxn],ans,aa,bb,cc;
int col[maxn],pos[maxn];
struct qnode
{
    int l,r,id;
} qu[maxn];
bool cmp(qnode a,qnode b)
{
    if(pos[a.l]==pos[b.l])
        return a.r<b.r;
    return pos[a.l]<pos[b.l];
}
ll gcd(ll x,ll y)
{
    ll tp;
    while(tp=x%y)
    {
        x=y;
        y=tp;
    }
    return y;
}
void update(int x,int d)
{
    ans-=num[col[x]]*num[col[x]];
    num[col[x]]+=d;
    ans+=num[col[x]]*num[col[x]];
}
int main()
{
    int n,m,i,j,bk,pl,pr,id;

    freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m))
    {
        memset(num,0,sizeof num);
        bk=ceil(sqrt(1.0*n));
        for(i=1;i<=n;i++)
        {
            scanf("%d",&col[i]);
            pos[i]=(i-1)/bk;
        }
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&qu[i].l,&qu[i].r);
            qu[i].id=i;
        }
        sort(qu,qu+m,cmp);
        pl=1,pr=0;
        ans=0;
        for(i=0;i<m;i++)
        {
            id=qu[i].id;
            if(qu[i].l==qu[i].r)
            {
                up[id]=0,dw[id]=1;
                continue;
            }
            if(pr<qu[i].r)
            {
                for(j=pr+1;j<=qu[i].r;j++)
                    update(j,1);
            }
            else
            {
                for(j=pr;j>qu[i].r;j--)
                    update(j,-1);
            }
            pr=qu[i].r;
            if(pl<qu[i].l)
            {
                for(j=pl;j<qu[i].l;j++)
                    update(j,-1);
            }
            else
            {
                for(j=pl-1;j>=qu[i].l;j--)
                    update(j,1);
            }
            pl=qu[i].l;
            aa=ans-qu[i].r+qu[i].l-1;
            bb=(ll)(qu[i].r-qu[i].l+1)*(qu[i].r-qu[i].l);
            cc=gcd(aa,bb);
            aa/=cc,bb/=cc;
            up[id]=aa,dw[id]=bb;
        }
        for(i=0;i<m;i++)
            printf("%I64d/%I64d
",up[i],dw[i]);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/P6174/p/7723856.html