[知识学习] 莫队
迟到的莫队知识总结
最近学习了一种说优雅也挺优雅,说暴力也挺暴力的算法:莫队算法
普通莫队
给出一个长度为\(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的袜子
只不过这个题要推柿子,就交给自己完成,这里就不展示代码了
带修莫队
先咕着,下午再更
(咕咕咕)