让菜鸡讲一讲莫队(非修改)

传说中,莫队算法能解决一切区间处理问题

这是一个优雅的暴力

那么我们先看一道题


蛤蛤的项链

HH有一串由各种漂亮的贝壳组成的项链。

HH相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。

HH不断地收集新的贝壳,因此,他的项链变得越来越长。

有一天,他突然提出了一个问题:

某一段贝壳中,包含了多少种不同的贝壳?

这个问题很难回答……因为项链实在是太长了。

于是,他只好求助睿智的你,来解决这个问题。

这一题有多组询问。


之前我们解决区间问题的时候常用的一种方法是种一棵线段树

但是现在这东西不满足加和性质,线段树不能用了!

其实要用也是可以的,只不过有很大的几率即使不TLE也会MLE

怎么办?

那么我们祭出我们的莫队算法


莫队的组成部分

首先,莫队有一对指针,现在我们把它们分别叫做l,r

一开始,它们在序列中像这样摆放:

 l
 1234352435142435123   <贝壳颜色v>
r

然后,莫队有一排计数君cnt[]

它们统计在l~r区间中每种颜色出现了多少次

如果一开始不把l放在r前面的话,那么cnt[]将不能很好的反映出l~r区间的颜色情况

具体为什么请自行脑补

当l,r移动的时候,

cnt[]的值可以很方便的作出修改

比如现在

l=2
r=5
cnt={0,0,1,2,1,0}
  l
 1234352435142435123   <v>
     r

如果我们要得到l=1,r=6的情况

那么我们可以把l向左移动,把r向右移动

l--,cnt[v[l]]++;r++,cnt[v[r]]++;
//简写为cnt[v[--l]]++,cnt[v[++r]]++;
"因为它们是扩张,所以要先移动再计数"
l=1
r=6
cnt={0,1,1,2,1,1}
 l
 1234352435142435123   <v>
      r

又如果我们要得到l=3,r=4的情况

那么我们可以把l向右移动,把r向左移动

cnt[v[l]]--,l++;cnt[v[r]]--,r--;
//简写为--cnt[v[l++]],--cnt[v[r--]];
"因为它们是缩小,所以要先计数再移动"
l=3
r=4
cnt={0,0,1,1,0,0}
   l
 1234352435142435123   <v>
    r

莫队还有一个统计答案sum

可以在区间修改的时候顺便修改它

比如现在

l=2
r=3
sum=2
cnt={0,0,1,1,0,0}
  l
 1234352435142435123   <v>
   r

当我们要把l向右移1格的时候,

颜色2的出现次数减少了1,

而由于减少后2的出现次数变成了0,那么sum就要减少1

if(--cnt[v[l++]]==0)sum--;
if(--cnt[v[r--]]==0)sum--;//同理

而当我们要把r向右移1格的时候,

颜色4的出现次数增加了1,

而由于增加前4还没有出现过,那么sum就要增加1

if(cnt[v[++r]]++==0)sum++;
if(cnt[v[--l]]++==0)sum++;//同理

有没有想过,l和r这么移来移去是为了什么?

如果这道题只有1次询问,我们大可以暴力。

而莫队这样做,是为了处理多组询问。

每一次询问都可以在上次询问的基础上通过移动l,r来回答。

辣么可见这个算法的复杂度就由它们的移动次数来决定。

如何减少l,r的移动次数

显然对于两次询问L,R和L′,R′,

知道了L,R的答案,

就可以暴力计算|L−L′|+|R−R′|次得出L′,R′的答案。

Fa♂现|L−L′|+|R−R′|是曼哈顿距离

把每个询问看作是二维平面上的点,那么我们的最小总时间,

就是这些点的最小曼哈顿距离生成树,

按照这个树的顺序做,复杂度变成了\(O(n\sqrt n)\)

不过这样是不是有点繁琐?

分块大法好!

把整个序列分块,把L所在的块为第一关键字,R为第二关键字排序。

为什么要分块不能直接排呢?

分块很好的减少了一种情况的影响:

L<L′<L′′,并且距离很近,但是R′<R<R′′,并且R′与另外两个离得很远,会导致r指针一顿乱跳。

如果直接按L排序就会浪费非常多的时间。

由于每个块的大小是\(\sqrt n\)

分块使得两个询问之间差异平均到了L,R上。

因此,理论复杂度大约还是是\(O(n\sqrt n)\)

给出蛤蛤的项链的代码,

这也可以作为非修改莫队的模板。

#include<bits/stdc++.h>
using namespace std;
inline int gotcha()
{
    register int a=0,b=1,c=getchar();
    while(!isdigit(c))b^=c=='-',c=getchar();
    while(isdigit(c))a=(a<<3)+(a<<1)+c-48,c=getchar();
    return b?a:-a;
}
const int _ = 50002 , __ = 2000002 , sect = 667;
struct trouble
{
    int l,r,sc,num;
    const int operator < (const trouble &b)const{return sc==b.sc?r<b.r:sc<b.sc;}
}q[__];
int n,m,sum=0,cnt[__]={0},col[__],ans[__];
void add(int a){if(++cnt[a]==1)sum++;}void del(int a){if(--cnt[a]==0)sum--;}
int main()
{
    register int i,l=1,r=0;
    for(n=gotcha(),i=1;i<=n;i++)col[i]=gotcha();
    for(m=gotcha(),i=1;i<=m;i++)q[i].l=gotcha(),q[i].r=gotcha(),q[i].sc=q[i].l/sect,q[i].num=i;
    sort(q+1,q+m+1);
    for(i=1;i<=m;i++)
    {
        while(r<q[i].r)add(col[++r]);while(r>q[i].r)del(col[r--]);
        while(l<q[i].l)del(col[l++]);while(l>q[i].l)add(col[--l]);
        ans[q[i].num]=sum;
    }
    for(i=1;i<=m;i++)printf("%d\n",ans[i]);
    return 0;
}
posted @ 2018-02-03 15:19  iot;  阅读(193)  评论(0编辑  收藏  举报
知识共享许可协议
年轻人,你需要更多的知识