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优化的效果是非常可观的,建议都用上。

//实在不会可以看我的介绍:输入输出优化

本篇完,见下一篇:莫队(三):莫队算法的拓展

0 0 votes
文章评分
订阅这个评论
提醒

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

1 评论
最旧
最新 得票最多
Inline Feedbacks
View all comments