传说中,莫队算法能解决一切区间处理问题
这是一个优雅的暴力
那么我们先看一道题
蛤蛤的项链
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(nsqrt n))。
不过这样是不是有点繁琐?
分块大法好!
把整个序列分块,把L所在的块为第一关键字,R为第二关键字排序。
为什么要分块不能直接排呢?
分块很好的减少了一种情况的影响:
L<L′<L′′,并且距离很近,但是R′<R<R′′,并且R′与另外两个离得很远,会导致r指针一顿乱跳。
如果直接按L排序就会浪费非常多的时间。
由于每个块的大小是(sqrt n)
分块使得两个询问之间差异平均到了L,R上。
因此,理论复杂度大约还是是(O(nsqrt 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
",ans[i]);
return 0;
}