莫队算法初识(对!没有模板)

来源:互联网 发布:mac safari关不了 编辑:程序博客网 时间:2024/05/18 16:36

         和主席树一样,这个算法在我的高中OI生涯中也是一个高端的代名词。很多次省赛,在遇到区间问题时,我总是觉得是线段树,但是自己连线段树都学的不好,发现并做不了。最后讲题人一说是莫队算法,全场瞬间醒悟,而我独自懵逼,并不知道是莫队是何物……

        有没有觉得我老是拿高中的糗事说事……好咯,我不该自嘲的……

        说正事,首先得膜拜发明者莫涛大牛Orz。这个算法享誉国内外,在外国人们称之为Mo‘s Algorithm,当我看到这个中国人名字的算法时,心里无比自豪。正如我前面所暗示的莫队算法是用来解决区间问题,他可以很简洁的用离线的方法解决几乎所有区间问题。时间复杂度的话,根据证明是O(N^1.5)

        现在具体来说,我们知道,对于朴素的方法,我们是对于每一个区间单独处理,那么我们处理完一个区间后再利用这个结果转移求下一个区间会怎么样呢?是的,你会发现,如果我们算出了区间[l,r]的结果,对于下一个区间[ll,rr],我们如果能够在O(1)或者O(N)的复杂度内完成单个区间的结果转移,那么总的复杂度就是O((|ll-l|+|rr-r|)*1)或者O((|ll-l|+|rr-r|)*logN),这样,我们是不是做到了一点点的优化。当然,你会觉得这并不是很优化,因为区间很多,而且这样子的移动可能也会有重复。经过观察,我们可以发现,每个区间[l,r]我们都能看成一个平面坐标上的点(l,r)显然我们要做到尽量少的移动区间,那么问题就变成了在一个二维平面上求汉密尔顿最小生成树的问题。根据汉密尔顿最小生成树算法,求这样一个最小生成树的时间复杂度的时间复杂度是O(NlogN),但是在莫队算法的实际应用中,时间复杂度是O(N^1.5)。

        算法的基本思路是这样,但是还是不让人满意,因为毕竟汉密尔顿最小生成树的编程复杂度还是很大的,这样子解决区间问题还是代价太大,有没有方法能简化但是时间复杂度还在同一量级呢?答案是肯定的。我们想到了用分块的思想,可以对所有的区间进行一次排序,这样我们也节约了移动区间的时间。我们按照通常方法,对于左端点分成sqrt(N)块,然后右端点作为第二关键字按照大小顺序排序,之后转移就行了。

        现在,你可能会发现,莫队算法很像暴力,其实它本质上就是优化后的暴力解法,但是却能解决可能需要好几个高级数据结构嵌套才能解决的问题,具有非常高的价值。但是正如你可能预见的,真正难的不是算法本身,难的是找到在O(logN)或者O(1)的时间内完成区间转移的方法,所以我就说,莫队算法没有固定的模板咯~。而且在一般情况下,O(logN)的转移甚至都不可取,因为加上这个logN之后,程序已经在超时的边缘了,所以说,算法本身也有它的局限性。最后,我来贴一下莫队算法的模板题吧,国家集训队题,小Z的袜子,BZOJ 2038 ,代码如下:

#include<iostream>#include<cstring>#include<cstdio>#include<algorithm>#include<cmath>#define maxn 50001#define ll long long using namespace std;int read(){    int x=0; char ch;    while (ch=(char)getchar(),ch<'0'||ch>'9');    while (x=x*10+ch-'0',ch=(char)getchar(),ch>='0'&&ch<='9');    return x; }struct data {int l,r,id; ll a,b;} a[maxn];int n,m,ans;int c[maxn],pos[maxn];ll s[maxn];bool cmp(data a, data b){    if (pos[a.l]==pos[b.l])         if (pos[a.l]&1) return a.r<b.r;        else return a.r>b.r;    return pos[a.l]<pos[b.l];}bool cmp_id(data a, data b){    return a.id<b.id;}ll sqr(ll x){return (ll)x*x;}void init(){    n=read(); m=read();    for (int i=1; i<=n; i++) c[i]=read();    int kk=int (sqrt(n));    for (int i=1; i<=m; i++)    {        a[i].l=read(); a[i].r=read(); a[i].id=i;    }    for (int i=1; i<=n; i++) pos[i]=(i-1) /kk+1;}void updata(int p,int add){    ans-=sqr(s[c[p]]);    s[c[p]]+=add;    ans+=sqr(s[c[p]]);}void work(){    int r=0,l=1;    for (int i=1; i<=m; i++)    {        for (; r<a[i].r; r++) updata(r+1,1);        for (; r>a[i].r; r--) updata(r,-1);        for (; l<a[i].l; l++) updata(l,-1);        for (; l>a[i].l; l--) updata(l-1,1);        if (a[i].l==a[i].r)        {            a[i].a=0; a[i].b=1;            continue;        }        a[i].a=ans-(a[i].r-a[i].l+1);        a[i].b=(ll)(a[i].r-a[i].l)*(a[i].r-a[i].l+1);        ll k=__gcd(a[i].a,a[i].b);        a[i].a/=k; a[i].b/=k;    }    }int main(){    init();    sort(a+1,a+m+1,cmp);    work();    sort(a+1,a+m+1,cmp_id);    for (int i=1; i<=m; i++)    {         printf("%lld/%lld\n",a[i].a,a[i].b);    }}

0 0
原创粉丝点击