目录
2、复杂度证明
//这里复杂度的证明我也比较迷糊,所以借鉴自大米的博客
1、莫队首先要按照关键字进行排序,众所周知\(sort\)的复杂度是\(O(nlogn)\)。
2、枚举m个答案为\(O(m)\),设分块大小为unit
分类讨论:
- ①左指针的移动:若下一个询问的\(l\)与当前询问的\(l\)不在同一个分块,需要经过最多\(2*unit\)步移动到下一个询问的位置,复杂度为\(O(m*unit)\)。
- ②右指针的移动:只有当\(l\)在同一分块的情况下,\(r\)才是有序的(其他情况下\(r\)会左右横跳,最坏情况下会跳\(n\)次),\(l\)在同一份块中时,\(r\)是单调递增的,因此枚举完一个块,\(r\)最多需要移动\(n\)次,总共有\(\frac{n}{unit}\)个块,所以复杂度为\(O(n*n/unit)\)。
由于\(n和m\)同级所以可以都用\(n\)表示,那么其总复杂度为\(O(n*unit+n*n/unit+nlogn)\)
由于分块时每个块都分为\(\sqrt n\)的大小,即\(unit=\sqrt n\),\(nlogn\)与前面的部分为加法关系且\(\sqrt n>logn\),所以我们得出莫队算法的复杂度为:
\(O(n\sqrt n)\)
3、终于等到你:代码实现
还是这道题目哦! https://www.luogu.org/problem/SP3267
#include<cmath>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define RE register
using namespace std;
const int M=1e6+10;
int n,m,l=1,r,now;
int a[M],cnt[M],ans[M],be[M];//我们使用数组be记录每个点在哪一个分块内
struct node{
int l,r,id;
}q[M];
//莫队的题目往往有大量的输入输出,所以I/O优化十分必要
inline int Read(){
char ch=getchar();
int x=0;
while(ch<'0'||ch>'9')
ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
void Print(int x){
if(x>=10)
Print(x/10);
putchar(x%10+'0');
}
//结构体以第一第二关键字进行排序
inline bool CMP(node a,node b){
return be[a.l]==be[b.l]?a.r<b.r:be[a.l]<be[b.l];
}
//加入新的数字
inline void Add(int x){
if(!cnt[a[x]]) now++;
cnt[a[x]]++;
}
//删掉退出的数字
inline void Del(int x){
cnt[a[x]]--;
if(!cnt[a[x]]) now--;
}
int main(){
n=Read();
for(RE int i=1;i<=n;i++)
a[i]=Read();
double sq=sqrt(n);
for(RE int i=1;i<=ceil((double)n/sq);i++)
for(RE int j=(i-1)*sq+1;j<=i*sq;++j)
be[j]=i;
m=Read();//因为我们的结构体用了q这个变量,这里用m代替题面中的q
for(RE int i=1;i<=m;i++){
q[i].l=Read();
q[i].r=Read();
q[i].id=i;
}
sort(q+1,q+m+1,CMP);
for(int i=1;i<=m;i++){
while(l<q[i].l) Del(l++);
while(l>q[i].l) Add(--l);
while(r<q[i].r) Add(++r);
while(r>q[i].r) Del(r--);
ans[q[i].id]=now;
}
for(int i=1;i<=m;i++){
Print(ans[i]);
putchar('\n');//用输出优化不要忘了换行
}
return 0;
}

本题完结撒花
这是没有吸氧气(\(O_2\)优化)的结果
莫队的优化
1、玄学排序法:奇偶性排序法
其实到现在我还是不懂为什么可以这样优化,但它确实有效,肯定是我太弱了!
优化方法:在排序的时候将CMP函数改成
inline bool CMP(node a,node b){
return (be[a.l]^be[b.l])?be[a.l]b.r;
}

你看效果多明显,优化掉了10ms呢!
略显尴尬,我听dalao说能优化掉几百毫秒,可能是题目的原因吧。不理,只要时间少了就是有优化,嘤嘤嘤!
2、移动指针的常数压缩
这个想法我第一次写就这么想的,结果被题解带入了Add()和Del()的大坑,听说这个还能优化几百毫秒,我去试试!

结果不错,这样写速度很快,在这里优化掉了20ms,代码量也很好看,所以我贴上来。注意改动的部分在main( )里
#include<cmath>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define RE register
using namespace std;
const int M=1e6+10;
int n,m,l=1,r,now;
int a[M],cnt[M],ans[M],be[M];//我们使用be记录点在哪一个分块内
struct node{
int l,r,id;
}q[M];
//输入输出优化没什么好说的吧,莫队的题目一般I/O都比较大,建议使用I/O优化
inline int Read(){
char ch=getchar();
int x=0;
while(ch<'0'||ch>'9')
ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
void Print(int x){
if(x>=10)
Print(x/10);
putchar(x%10+'0');
}
inline bool CMP(node a,node b){
return (be[a.l]^be[b.l])?be[a.l]<be[b.l]:(be[a.l]&1)?a.r<b.r:a.r>b.r;
}
int main(){
n=Read();
for(RE int i=1;i<=n;i++)
a[i]=Read();
double sq=sqrt(n);
for(RE int i=1;i<=ceil((double)n/sq);i++)
for(RE int j=(i-1)*sq+1;j<=i*sq;++j)
be[j]=i;
m=Read();//因为我们struct用了q了所以我们用m代替题面中的q
for(RE int i=1;i<=m;i++){
q[i].l=Read();
q[i].r=Read();
q[i].id=i;
}
sort(q+1,q+m+1,CMP);
/* for(int i=1;i<=m;i++){
while(l<q[i].l) Del(l++);
while(l>q[i].l) Add(--l);
while(r<q[i].r) Add(++r);
while(r>q[i].r) Del(r--);
ans[q[i].id]=now;
}*/
for(int i=1;i<=m;i++){
int ql=q[i].l,qr=q[i].r;
while(l<ql) now-=!--cnt[a[l++]];
while(l>ql) now+=!cnt[a[--l]]++;
while(r<qr) now+=!cnt[a[++r]]++;
while(r>qr) now-=!--cnt[a[r--]];
ans[q[i].id]=now;
}
for(int i=1;i<=m;i++){
Print(ans[i]);
putchar('\n');//用输出优化别忘了换行
}
return 0;
}
被注释掉的代码是朴素做法的部分,已经被优化取代掉了。
3、输入输出优化
都学了莫队了I/O优化肯定没问题,我在我的程序里也用到了。用到莫队的题目输入输出量都比较大,I/O优化的效果是非常可观的,建议都用上。
//实在不会可以看我的介绍:输入输出优化
