[知识学习] 莫队

迟到的莫队知识总结

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

普通莫队

例题

给出一个长度为(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
",res[i]);
	}
	return 0;
}

普通莫队是不是很简单?

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

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


带修莫队

先咕着,下午再更

(咕咕咕)

转载请注明出处--Xx_queue
原文地址:https://www.cnblogs.com/Xx-queue/p/12205076.html