莫队算法

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

例题 (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(n\sqrt 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(n\sqrt{n})\)(这个我不会证明),但是这种方法的编程复杂度稍微高了一点。

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

一、\(i\)\(i+1\)在同一块内,\(r\)单调递增,所以\(r\)\(O(n)\)的。由于有\(\sqrt{n}\)块,所以这一部分时间复杂度是\(O(n\sqrt{n})\)
二、\(i\)\(i+1\)跨越一块,\(r\)最多变化\(n\),由于有\(\sqrt{n}\)块,所以这一部分时间复杂度是\(O(n\sqrt{n})\)
三、\(i\)\(i+1\)在同一块内时,变化不超过\(\sqrt{n}\),跨越一块也不会超过\(\sqrt{n}\),不妨看作是\(\sqrt{n}\)。由于有n个数,所以时间复杂度是\(O(n\sqrt{n})\)
于是就变成了\(O(n\sqrt{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\n",up[i],dw[i]);
    }
    return 0;
}
posted @ 2017-10-24 15:35  Planet6174  阅读(742)  评论(0编辑  收藏  举报