莫队相关
大多数内容来源于:LawrenceSivan
莫队简介
莫队是一种对询问的分块算法,通过左右端点的移动维护区间信息,主要用来处理离线区间查询等问题。
莫队算法由莫涛队长提出,所以称这种算法为莫队
普通莫队
操作过程
将询问离线存储,然后将总区间分块,对\(belong[l]\)进行排序以减少左右端点移动次数,然后对于每个块进行处理就可以得到询问的答案
代码实现
- 分块
block=n/sqrt(m*2/3);
for(int i=1;i<=n;i++){
belong[i]=(i-1)/block;
}
块长非常玄学但会更快
- 初始化与记录数组
ans用来记录答案
cnt用来记录过程中的变量(雾
int a[N],cnt[N],lastl=1,lastr=0;
ll ans[N],sum;
- 莫队的结构体
id用来存储下标
struct node{
int l,r,id;
inline bool operator < (const node &other)const{
return belong[l]==belong[other.l]?r<other.r:l<other.l;
}
}mo[N];
有一种玄学的奇偶优化,按奇偶块排序,如果区间左端点所在块不同直接按左端点从小到大排,如果相同,奇块按右端点从小到大排,偶块按右端点从大到小排。
我不知道好不好用反正我没用过
inline bool operator < (const node & other)const{
return belong[l]^belong[other.l]?belong[l]<belong[other.l]:((belong[l]&1)?r<other.r:r>other.r)
}
- 询问和维护操作
for(int i=1;i<=m;i++){
int l=mo[i].l,r=mo[i].r;
while(lastl>l){lastl--;add(a[lastl]);}
while(lastl<l){del(a[lastl]);lastl++;}
while(lastr<r){lastr++;add(a[lastr]);}
while(lastr>r){del(a[lastr]);lastr--;}
ans[mo[i].id]=sum;
}
唔……大概就是这种感觉……
可以想象为一个动态的移动的区间(会爬!),到某个点是添加完成了某个点,所以要注意先改坐标还是先改值。
add和del视题而定
复杂度证明
区间分块可以得到\(\sqrt{n}\)个块,那么存在\(\sqrt{n}\)个区间,对于每个区间的\(l,r\)的最坏情况是每个块都遍历到序列最右端,共\(n\)个点,\(m\)次查询,所以复杂度为\(\text{O}{(n+m)\sqrt{n}}\)的.
如果分成\(\frac{n}{\sqrt m}\)是\(\text{O}{(n \sqrt {m})}\)的。
例题
LgP2709 小B的询问
Code
- 询问数字在区间内出现的次数
\[\mathcal{show\enspace you\enspace the\enspace code}
\]
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+5;
typedef long long ll;
int n,m,k,block;
ll ans[N],sum;
int a[N],cnt[N];
int lsl=1,lsr=0;
struct node{
int l,r,id;
inline bool operator < (const node &other)const{
return ((l-1)/block==(other.l-1)/block)?r<other.r:l/block<other.l/block;
}
}mo[N];
inline void add(int x){
sum+=cnt[x]*2+1;
cnt[x]++;
}
inline void del(int x){
sum-=cnt[x]*2-1;
cnt[x]--;
}
int main(){
n=read();m=read();k=read();
block=sqrt(n);
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=m;i++){
mo[i].l=read();
mo[i].r=read();
mo[i].id=i;
}
sort(mo+1,mo+m+1);
for(int i=1;i<=m;i++){
int l=mo[i].l,r=mo[i].r;
while(lsl<l){
del(a[lsl]);
lsl++;
}
while(lsl>l){
lsl--;
add(a[lsl]);
}
while(lsr<r){
lsr++;
add(a[lsr]);
}
while(lsr>r){
del(a[lsr]);
lsr--;
}
ans[mo[i].id]=sum;
}
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
return 0;
}
SP3267 DQUERY
- 询问区间内有多少不同的数字
较于上一题更改一下add和del即可
Code
\[\mathcal{show\enspace you\enspace the\enspace code}
\]
inline void add(int x){
sum+=(++cnt[x]==1);
}
inline void del(int x){
sum-=(--cnt[x]==0);
}
LgP1494 小Z的袜子
- 询问区间内有多大概率抽到相同的数字
题解
设区间为\([l,r]\),则长度len为\(r-l+1\)
很明显可以得到计算公式:
\[ans=\frac{\sum_{i=1}^{i \le n}\frac{cnt[i]\times (cnt[i]-1)}{2}}{\frac{len\times (len-1)}{2}}
\]
然后可以发现分母可以直接计算所以我们只需要维护分子,然后用gcd计算答案即可。每一次分子中的cnt变量只会改动一个,直接维护。
Code
\[\mathcal{show\enspace you\enspace the\enspace code}
\]
inline void add(int x){
sum-=cnt[x]*(cnt[x]-1)/2;
cnt[x]++;
sum+=cnt[x]*(cnt[x]-1)/2;
}
inline void del(int x){
sum-=cnt[x]*(cnt[x]-1)/2;
cnt[x]--;
sum+=cnt[x]*(cnt[x]-1)/2;
}
有一说一我一开始还推公式来着然后发现这多简单
带修莫队
我们上面刚说了这东西是离线的然后

浙公网安备 33010602011771号