莫队算法

Posted genius777

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了莫队算法相关的知识,希望对你有一定的参考价值。

什么是莫队,就是暴力嘛!!!学完后很多人都会这么说,就是有着一个很迷性质的优化,优化完了你都不知道为什么的优化。

莫队算法的效率就取决与你分块的方式了,那么我们就来看看莫队是怎样实现优化的。

先来看一道题:小B的询问

题目描述

小B有一个序列,包含N个1~K之间的整数。他一共有M个询问,每个询问给定一个区间[L..R],求Sigma(c(i)^2)的值,其中i的值从1到K,其中c(i)表示数字i在[L..R]中的重复次数。小B请你帮助他回答询问。

输入输出格式

输入格式:

 

第一行,三个整数N、M、K。

第二行,N个整数,表示小B的序列。

接下来的M行,每行两个整数L、R。

 

输出格式:

 

M行,每行一个整数,其中第i行的整数表示第i个询问的答案。

 

输入输出样例

输入样例#1: 
6 4 3
1 3 2 1 1 3
1 4
2 6
3 5
5 6
输出样例#1:
6
9
5
2

说明

对于全部的数据,1<=N、M、K<=50000


 

如果你还没有学过莫队(如果学过了,还看什么博客啊!!!),这道题多半的第一反应是线段树之类的数据结构,但会发现这道题的区间合并很难实现,因为单纯质朴线段树并不资瓷某个数出现次数的合并,当然要是你写不清真的树套树(权值线段树套区间线段树)还是可以过的,说不定还贼快,但是莫队同样可以过的,我们干嘛大费周章地去写树套树呢???

首先我们来看看暴力是怎么实现的:

我们首先有两个指针,为现在处理到了的区间左端点和右端点,如果当前处理到的提问的区间的左右端点不重合,那么我们就跳跳跳!!!将指针移到需要求的区间对应左右端点,如图所示:

 

如果我们的左指针小于区间左端点,将左端点向后跳

如图,很显然我们会发现我们向右移动后,我们失去了一个黄色的块(与数字是一样的),所以黄色块的数量减一,判断一下减去这个黄色块之后,如果区间黄色块的个数变为零了,那么总的种类减一。

如果我们的右指针小于区间左端点,将右端点向后跳

如图,很显然我们会发现我们向右移动后,我们多了一个橙色的块(与数字是一样的),所以橙色块的数量加一,判断一下加上这个橙色块之后,如果区间橙色块的个数变为一了,那么总的种类加一。

 

左右端点的左移同理。

【莫队登场】

显然上述的暴力我们的左右指针会在区间里疯狂乱跳,跳而dying,结果肯定GG。

那么现在就需要莫队的奇妙性质来优化了,不难想到我们需要的是让指针尽量地少地跳,最好一路跳过去不用往回跳那就是O(n)的了(不过这在题目里基本不现实)。

这里发现在线的算法并不能很好地满足这样的性质,我们考虑把询问离线下来,然后对其进行排序,使其尽量优秀一点。

我们将询问的左端点进行分块操作,块的大小可以是sqrt(n)也可以是n^(2/3),但据说后者好像在带修改时最为理想。

然后我们对其排序,以左端点的所属块的顺序为第一关键字,右端点为第二关键字。

1 bool cmp(sd a,sd b)
2 {
3     if(be[a.l]==be[b.l]) return a.r<b.r;
4     return be[a.l]<be[b.l]; 
5 }

当然这里排序的关键字多种多样,每道题对于不同的排序方法复杂度还不一样,所以这就是莫队很迷的地方,好像也没看到网上有统一的排序方式,只好随缘哦。。。

如果不慎,就像我一样,死在分块的道路上,TLE在黄泉路上,拉都拉不回来!!!以图为证:


 

莫队时间复杂度证明:(这里是借用别人的证明,看看就好)

0.首先我们知道在转移一个单位距离的时候时间复杂度是O(1)

1:对于左端点在同一块中的询问:
右端点转移的时间复杂度是O(n),一共有√n块,所以右端点转移的时间复杂度是O(n√n)
左端点每次转移最差情况是O(√n),左端点在块内会转移n次,左端点转移的时间复杂度是O(n√n);

2:对于左端点跨越块的情况:
会跨区间O(√n)次;
左端点的时间复杂度是O(√n*√n)=O(n)可以忽略不计
右端点因为在跨越块时是无序的,右端点跨越块转移一次最差可能是O(n),可能跨越块转移√n次,所以时间复杂度是O(n√n)

所以总的来说莫队的时间复杂度是O(n√n);

【代码实现】

 1 #include<cstdio>
 2 #include<cmath>
 3 #include<cctype>
 4 #include<algorithm>
 5 #define N 50005
 6 #define LL long long 
 7 using namespace std;
 8 int read()
 9 {
10     int c,x,f;
11     while(!isdigit(c=getchar())&&c!=\'-\'); c==\'-\'?(x=0,f=1):(x=c-\'0\',f=0);
12     while(isdigit(c=getchar())) x=(x<<3)+(x<<1)+c-\'0\'; return f?-x:x;
13 }
14 int n,m,k,x[N],cnt[N];
15 LL ANS[N];
16 struct que{
17     int l,r,id,b;
18 }q[N];
19 bool cmp(que a,que b)
20 {
21     if(a.b==b.b) return a.r<b.r;
22     return a.l<b.l;
23 }
24 int main()
25 {
26     n=read(); m=read(); k=read();
27     int size=sqrt(n);
28     for(int i=1;i<=n;i++) x[i]=read();
29     for(int i=1;i<=m;i++)
30     q[i].l=read(),q[i].r=read(),q[i].id=i,q[i].b=(q[i].l-1)/size+1;
31     sort(q+1,q+m+1,cmp);
32     int l=1,r=0; LL ans=0;
33     for(int i=1;i<=m;i++)
34     {
35         while(l>q[i].l) l--,cnt[x[l]]++,ans+=2*cnt[x[l]]-1;
36         while(r<q[i].r) r++,cnt[x[r]]++,ans+=2*cnt[x[r]]-1;
37         while(r>q[i].r) cnt[x[r]]--,ans-=2*cnt[x[r]]+1,r--;
38         while(l<q[i].l) cnt[x[l]]--,ans-=2*cnt[x[l]]+1,l++;
39         ANS[q[i].id]=ans; 
40     }
41     for(int i=1;i<=m;i++) printf("%lld\\n",ANS[i]);
42     return 0;
43 } 

 

以上是关于莫队算法的主要内容,如果未能解决你的问题,请参考以下文章

CSU 1515 Sequence (莫队算法)

CodeForces - 220B 离散化+莫队算法

算法笔记莫队算法(基础莫队,带修莫队,回滚莫队,树上莫队,二次离线莫队)

莫队算法~讲解

P1494 [国家集训队]小Z的袜子(莫队算法)

莫队