化暴力为标算——莫队

来源:互联网 发布:混也是一种生活 知乎 编辑:程序博客网 时间:2024/06/05 22:39

莫队。。这个听起来非常高端的东西,当时听大佬们说这个名词就一脸害怕,学完发现其实也还好。

之所以叫这个名字 因为发明者的缘故而已。。和算法本身并没有什么直接关系。
莫队就是用区间来优化暴力,可以把暴力的复杂度压到n^1.5左右
当然,常数有多大我就不知道了

莫队算法的本质就是暴力枚举。
对于m个询问,就是把m用一种方法排序后,直接暴力

考虑一下,,如果说我们知道 l~r之间的答案,
那么可以用O(1)的时间来得到l-1~r,l~r+1的答案
所以对询问排序啊!
好。。我知道会被一些极端数据卡死O(n^2)
但是反之,对于另一种极端情况是不是可以很快?O(n)
莫队的作用就是让复杂度均摊,于是就出现了O(n^1.5)

排序方法就是:分块
例如我们将n个数分成长度为n^0.5的n^0.5(+1)块
然后按每个询问的l所在区间排序
是不是就可以了?
每次跳一个区间的复杂度最多不过是n^0.5而已
我用lin[i]表示第i个数所在区间,排序的判断如下:

inline bool cmp(node x,node y){    if(lin[x.l]==lin[y.l])  return x.r<y.r;    return lin[x.l]<lin[y.l];}

接下来,就是暴力了。。
是不是感觉很简单=v=+

下面以bzoj3289 mato的文件管理为例
传送门——bzoj3289

题意:给定一个序列a[1..n],询问l到r区间内逆序对数量
逆序对。。第一反应merge。。然而显然不可行
然后看了黄学长的博客,才想到树状数组求逆序对
树状数组:

inline int lowbit(int x){    return x&-x;}inline int get(int x){    int sum=0;    for(int i=x;i;i-=lowbit(i))        sum+=f[i];    return sum;}inline void add(int x,int v){    for(int i=x;i<=n;i+=lowbit(i))          f[i]+=v;}

我们可以记录rank[i]表示a[i]在升序序列中的位置
那就方便了啊,当要用l~r求l~r+1的时候,就可以add(rank[r+1],1),反之,求l-1~r时,就可以add(rank[l-1],-1);

于是就变成了一道莫队的水题啊。。在n^1.5复杂度内解决问题

inline void doit(){    int ans=0;    sort(q+1,q+m+1,cmp);    for(int i=1,l=1,r=0;i<=m;i++)    {        for(;l<q[i].l;l++)  {add(rank[l],-1);ans-=get(rank[l]-1);   }        for(;r>q[i].r;r--)  {add(rank[r],-1);ans=ans-(r-l-get(rank[r]));    }        while(l>q[i].l) {l--;add(rank[l],1);ans+=get(rank[l]-1);    }        while(r<q[i].r) {r++;add(rank[r],1);ans+=(r-l+1-get(rank[r]));  }        q[i].ans=ans;    }}

接下来输出:

inline bool cmp2(node x,node y){    return x.z<y.z;}
    sort(q+1,q+m+1,cmp2);    for(int i=1;i<=m;i++)        printf("%d\n",q[i].ans);

CODE:

#include<iostream>#include<cstdio>#include<cmath>#include<cstring>#include<algorithm>using namespace std;struct node{    int l,r,z,ans;}q[50001];int a[50001],t[50001],lin[50001],f[50001];int n,m;int read(){    int x=0,f=1;char ch=getchar();    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}    return x*f;}inline bool cmp2(node x,node y){    return x.z<y.z;}inline bool cmp(node x,node y){    if(lin[x.l]==lin[y.l])  return x.r<y.r;    return lin[x.l]<lin[y.l];}inline int lowbit(int x){    return x&-x;}inline int get(int x){    int sum=0;    for(int i=x;i;i-=lowbit(i))        sum+=f[i];    return sum;}inline void add(int x,int v){    for(int i=x;i<=n;i+=lowbit(i))          f[i]+=v;}inline void doit(){    int ans=0;    sort(q+1,q+m+1,cmp);    for(int i=1,l=1,r=0;i<=m;i++)    {        for(;l<q[i].l;l++)  {add(a[l],-1);ans-=get(a[l]-1); }        for(;r>q[i].r;r--)  {add(a[r],-1);ans=ans-(r-l-get(a[r]));  }        while(l>q[i].l) {l--;add(a[l],1);ans+=get(a[l]-1);  }        while(r<q[i].r) {r++;add(a[r],1);ans+=(r-l+1-get(a[r]));    }        q[i].ans=ans;    }}int main(){    n=read();    for(int i=1;i<=n;i++)   t[i]=a[i]=read();    sort(t+1,t+n+1);    for(int i=1;i<=n;i++)   a[i]=lower_bound(t+1,t+n+1,a[i])-t;    m=read();    for(int i=1;i<=m;i++)    {        q[i].l=read();q[i].r=read();        q[i].z=i;    }    int block=sqrt(n);    //tot=tot+(block%n!=0);    for(int i=1;i<=n;i++)        lin[i]=(i-1)/block+1;    doit();    sort(q+1,q+m+1,cmp2);    for(int i=1;i<=m;i++)        printf("%d\n",q[i].ans);}

复杂度证明:

显然,l向前移动不会超过一个区间的长度。所以l最多移动次数:n^1.5,此时每次向前移动都移动至区间最右端,并且每次移到最左。
r同理
所以l和r移动次数大约在n*sqrt(n)的范围内,接下来就是乘上每次修改的复杂度了,以上题为例,复杂度在极端情况下为n*sqrt(n)*log(n)
这就是莫队算法了。。
谢谢看到这里

0 0
原创粉丝点击