莫队相关

大多数内容来源于:LawrenceSivan

莫队简介

莫队是一种对询问的分块算法,通过左右端点的移动维护区间信息,主要用来处理离线区间查询等问题。

莫队算法由莫涛队长提出,所以称这种算法为莫队

普通莫队

操作过程

将询问离线存储,然后将总区间分块,对\(belong[l]\)进行排序以减少左右端点移动次数,然后对于每个块进行处理就可以得到询问的答案

代码实现

  1. 分块
block=n/sqrt(m*2/3);
for(int i=1;i<=n;i++){
	belong[i]=(i-1)/block;
}

块长非常玄学但会更快

  1. 初始化与记录数组

ans用来记录答案
cnt用来记录过程中的变量(雾

int a[N],cnt[N],lastl=1,lastr=0;
ll ans[N],sum;
  1. 莫队的结构体

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)
}
  1. 询问和维护操作
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;
}

唔……大概就是这种感觉……
可以想象为一个动态的移动的区间(会爬!),到某个点是添加完成了某个点,所以要注意先改坐标还是先改值。
adddel视题而定

复杂度证明

区间分块可以得到\(\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

  • 询问区间内有多少不同的数字
    较于上一题更改一下adddel即可

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;
}

有一说一我一开始还推公式来着然后发现这多简单

带修莫队

我们上面刚说了这东西是离线的然后

posted @ 2021-09-16 11:50  无琛  阅读(95)  评论(2)    收藏  举报