[知识学习] 莫队

迟到的莫队知识总结

最近学习了一种说优雅也挺优雅,说暴力也挺暴力的算法:莫队算法

普通莫队

例题

给出一个长度为\(n\)的数列,\(a_1,a_2,...,a_n\),有\(q\)个询问,每个询问给出数对\((i,j)\),需要你给出\(a_i,a_{i+1},...,a_j\)这一段中有多少不同的数字

当然你也可以做HH的项链,题意是一样的,只是这个题目卡了莫队算法

  • 首先考虑一下暴力

对于每个询问,直接从\(l\)\(r\)扫一遍,统计 复杂度\(O(nq)\) 显然不行(废话

  • 考虑优化

维护两个指针\(l\)\(r\),现在\(l\)\(r\)统计的区间就是\([l,r]\),每次往新的询问区间去靠近,分四种情况:

1.\([l,r]->[l+1,r]\) :统计答案时先减去现在\(l\)位置的答案,再把\(l++\)

2.\([l,r]->[l,r+1]\) :统计答案是先把\(r++\),在加上\(r++\)之后的位置的答案

3.\([l,r]->[l-1,r]\) :统计答案是先把\(l--\),在加上\(l--\)之后的位置的答案

4.\([l,r]->[l,r-1]\) :统计答案时先减去现在\(r\)位置的答案,再把\(r--\)

总结一下规律:区间扩大时就先\(l--\)(\(r++\)),再加上答案,区间缩小时就先减去答案,再\(l++\)(\(r--\))

这样就优化了不少,但对于询问区间一左一右来来回回跳动的情况,又退化成了\(O(nq)\)

  • 进一步优化

因为可以离线计算,所以可以先把询问区间存下来,排个序,按一定顺序进行询问,就可以避免出现一左一右询问的情况

但是按什么来排序?每个区间的左端点顺序?

显然不行吧,万一右端点又是一左一右的乱跳呢?

这时,分块的思想就派上用场了;

我们对询问进行分块,如果两个询问左端点在同一块,就按右端点的块排序,否则就按左端点的块排序

这样就会每个块内指针跳动了,这也就是莫队算法的精髓

贴个代码:

#include <bits/stdc++.h>
#define N (1000000+5)
using namespace std;
typedef long long LL;
int n,m,l,r,bl;
LL ans;
int pos[N],cnt[N],a[N];
LL res[N];
struct node{
	int l,r,id;
}q[N];
bool cmp(node x,node y){//对询问排序
	if(pos[x.l]!=pos[y.l]) return pos[x.l]<pos[y.l];
	return pos[x.r]<pos[y.r];
}
inline void add(int x){if(++cnt[x]==1) ans++;}//加答案
inline void del(int x){if(--cnt[x]==0) ans--;}//减答案
void solve(int l,int r){
	sort(q+1,q+m+1,cmp);
	for(int i=1;i<=m;i++){
		node now=q[i];
		while(l>now.l) add(a[--l]);//四个操作
		while(r<now.r) add(a[++r]);
		while(l<now.l) del(a[l++]);
		while(r>now.r) del(a[r--]);
		res[q[i].id]=ans;
	}
}
int main(){
	scanf("%d",&n);
	bl=sqrt(n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
		pos[i]=(i-1)/bl+1;//分块
	}
	solve(1,0);
	for(int i=1;i<=m;i++){
		printf("%lld\n",res[i]);
	}
	return 0;
}

普通莫队是不是很简单?

洛谷还有一道模板题:小Z的袜子

只不过这个题要推柿子,就交给自己完成,这里就不展示代码了


带修莫队

先咕着,下午再更

(咕咕咕)

posted @ 2020-01-17 11:48  Xx_queue  阅读(84)  评论(0编辑  收藏  举报