莫队讲解

感谢@Misaka_Azusa dalao耐心细致的讲解。


它简单粗暴却又快速高效,它身为暴力却又高贵优雅,它清新脱俗,被尊称为“优雅的暴力”。它就是由莫涛队长在考场上发明的(据说)发明的——莫队算法


何为莫队

莫队\(!=\)提莫队长

莫队算法主要是用于离线解决通常不带修改只有查询的一类区间问题。当然,后来人也发明了能够解决带修改的莫队算法,简称带修莫队

传说中莫队算法能解决一切区间处理问题。至少它是可以解决维护一些线段树树状数组维护不了的东西或者是难维护的东西。

莫队还有许多改进版本,such as带修莫队、树上莫队等等

莫队的思想

莫队是通过合理安排询问的区间,来达到一个比较优的、可以接受的时间复杂度。莫队的核心是两个指针。它通过两个指针的来回移动来处理每一个区间询问。

那这两个指针怎么实现呢?

首先,我们将左指针放在第一个元素下,右指针指向第一个元素前的位置(当然,你也可以全放在第一个元素下面或第一个元素前,看个人喜好)。然后右指针每向右移动一位,就将这个元素加进来,每向左移动一位,就将这个元素减去,左指针与之相反。这样我们就可以很方便的处理这个问题了,只要让两个指针来回移动到询问的区间不就可以了吗?

但是如果我故意出一组数据,类似于\([1,100]、[1,2]、[2,100]、[6,7]\),这样右指针就会先从右蹦到左,从左蹦到右,又再次蹦回去,这就会浪费很多时间,妥妥的\(\rm{T}\)飞掉。但是如果我么合理安排一下询问的区间,就可以去掉大量不必要的计算。我们可以按照\([1,2]、[6,7]、[1,100]、[2,100]\)的顺序来跳动指针,就会快很多。

那这个顺序是怎么得出来的呢?这个问题也是莫队算法的一个核心,它的处理方法就是分块。我们将连续的几个数组下标分到一个块里,排序时左端点按块的编号排序,右端点按数组下标排序(还有一种做法是右端点也按块的编号排序),按照这个顺序来处理区间。这就要求我们将询问记录下来,所以说莫队算法是离线

应用

看一下这个图,假设我们要查找\(n\)个区间内出现的不同数字的个数,你会怎么办呢?

  • 树状数组?不可能的,它既不能用前缀和,也不能用查分。

  • 线段树?它貌似也不满足区间加法

  • 所以正解是——莫队

具体代码实现如下

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int read()
{
    int k=0;
    char c=getchar();
    for(;!isdigit(c);c=getchar())
     ;
    for(;isdigit(c);c=getchar())
      k=((k<<3)+(k<<1))+(c-48);
    return k;
}
struct zzz{
    int l,
        r,
        pos;
}que[300001]; //储存询问:左区间、右区间、询问的顺序

int bl;//块的大小
int n,m; 
bool cmp(zzz x,zzz y)  //按块排序
{
    if(x.l/bl!=y.l/bl) return x.l<y.l;
    else return x.r<y.r;
}
int a[600001],ans[300001],anss,tong[1000011]; 
//a:原序列,ans:将答案也记录下来,anss:当前区间的答案,tong:当前数字出现的次数
int xl=1,xr=0; //两个萌萌的指针
int main()
{
    n=read(); 
    for(int i=1;i<=n;i++)
      a[i]=read();
    m=read();
    for(int i=1;i<=m;i++)//输入+记录指针
    {
        que[i].l=read(), que[i].r=read();
        que[i].pos=i;
    }
    bl=sqrt(n);//不带修:开平方;  带修:开2/3次方(既平方的立方根)
    sort(que+1,que+m+1,cmp); //排序
    for(int i=1;i<=m;i++) //莫队主体
    {
        int l=que[i].l,r=que[i].r; 
        while(xl<l) //左指针向右移动
        {
            if(--tong[a[xl]]==0)
              anss--;
            xl++;
        }
        while(xl>l) //左指针向左移动
        {
            xl--;
            if(++tong[a[xl]]==1)
              anss++;
        }
        while(xr<r) //右指针向右移动
        {
            xr++;
            if(++tong[a[xr]]==1)
              anss++;
        }
        while(xr>r) //右指针向左移动
        {
            if(--tong[a[xr]]==0)
              anss--;
            xr--;
        }
        ans[que[i].pos]=anss; //记录当前答案
    }
    for(int i=1;i<=m;i++)
     printf("%d\n",ans[i]);
    
    return 0;
}
\\此题原题为:[SDOI2009]HH的项链
\\网址:https://www.luogu.org/problemnew/show/P1972
\\不过它现在用莫队已经过不去了 orz

程序中有个地方需要注意,那就是指针移动的次序,有的先移动指针,再更新答案,有的先更新答案,再移动指针,具体原因是——自己想去吧(逃

推荐博客

莫队算法~讲解\(by {~~}Misaka\,Azusa\,dalao\)

猜你喜欢

转载自www.cnblogs.com/wxl-Ezio/p/9021963.html