学习心得 - 算法 - 莫队

莫队

对于不好直接维护的区间问题把问题离线下来,再通过较快的单点操作完成对问题的求解。

一般是维护两个当前查询指针 \(l\) 和 \(r\),每一次通过对 \(l\) 和 \(r\) 的变动,维护 \([l,r]\) 信息,动态求出每一次查询的 \([ql,qr]\) 信息。

最经典的例题就是 \(q\) 个查询区间不同数字个数。这个可以把询问排序,容易动态维护出 \([l,r]\) 内的不同数字个数,这一点在 ABC 里经常考。

之后,我们发现这个东西还是太暴力了,一旦 update 复杂度不够优,就直接炸了。那么我们就需要在莫队上套一个分块。这样就能把左端点在同一块的放在一起,然后用分块的思想快速求出答案,复杂度可以压缩到 \(\mathcal O(n\sqrt{q}\times f(\tt Update))\)。为什么呢?我们发现令块内的右端点单调,这样一个块就最多移动 \(n\) 次,那么总共移动次数为 \(\frac{n^2}{\sqrt{q}}\),最终 \(\mathcal O(mq+\sqrt{n^2}{m})\),当 \(m=\sqrt{n^2}{q}\),复杂度就是 \(\mathcal O(q)\)。

P4462 [CQOI2018] 异或序列

这题是求区间内有多少个子区间的异或值为 \(k\),显然可以先对整个序列进行前缀和,然后求有多少对 \(l\) 和 \(r\) 满足 \(a_{l-1}\oplus a_{r}=k\)。我们考虑用莫队维护一个数出现的次数和在之前和这个值异或为 \(k\) 的个数,然后维护即可。

::::success[参考代码]

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e5+10;
int n,m,k,a[N];
ll ans[N],su;
int box[N];
int blk,bel[N];
struct qry{
	int l,r,i;
}q[N];
bool operator<(qry x,qry y){
	if(bel[x.l]!=bel[y.l])
		return bel[x.l]<bel[y.l];
	if(bel[x.l]&1)
		return x.r<y.r;
	return x.r>y.r;
}
void upd(int p){
	su+=box[a[p]^k];
	box[a[p]]++;
}
void del(int p){
	box[a[p]]--;
	su-=box[a[p]^k];
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m>>k;
	blk=pow(n,2/3.0);
	for(int i=1;i<=n;i++)	
		bel[i]=(i-1)/blk+1;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i]^=a[i-1];
	}
	for(int i=1;i<=m;i++)
		cin>>q[i].l>>q[i].r,
		q[i].i=i;
	sort(q+1,q+m+1);
	int L=0,R=0;
	box[0]=1;
	for(int i=1;i<=m;i++){
		while(L<q[i].l-1)del(L++);
		while(L>q[i].l-1)upd(--L);
		while(R<q[i].r)upd(++R);
		while(R>q[i].r)del(R--);
		ans[q[i].i]=su;
	}
	for(int i=1;i<=m;i++)
		cout<<ans[i]<<"\n";
	return 0;
}

::::

带修/二维莫队

我们需要将除了最后一维分块,复杂度是 \(\mathcal O(n^\frac{5}{3})\)。但是复杂度容易被炸飞。而带修莫队相当于在时间维度上的变动。

::::success[参考代码]

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=3e5+10;
int n,m,a[N];
ll ans[N],su;
int box[N*10];
int blk,bel[N];
struct qry{
	int l,r,i,t;
}q[2][N];
int cur[2];
bool operator<(qry x,qry y){
	if(bel[x.l]!=bel[y.l])
		return x.l<y.l;
	if(bel[x.r]!=bel[y.r])
		return x.r<y.r;
	return x.t<y.t;
}
void upd(int p){
	su+=!box[p];
	box[p]++;
}
void del(int p){
	box[p]--;
	su-=!box[p];
}
void tim(int i,int p){
	if(q[0][i].l<=q[1][p].l&&q[1][p].l<=q[0][i].r){
		del(a[q[1][p].l]);
		upd(q[1][p].r);
	}
	swap(a[q[1][p].l],q[1][p].r);
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	blk=pow(n,2/3.0);	
	for(int i=1;i<=n;i++)	
		bel[i]=i/blk;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=m;i++){
		char op;
		int l,r;
		cin>>op>>l>>r;
		if(op=='Q')
			++cur[0],q[0][cur[0]]={l,r,cur[0],cur[1]};
		else
			q[1][++cur[1]]={l,r,0,0};
	}
	sort(q[0]+1,q[0]+cur[0]+1);
	int L=1,R=0,T=0;
	box[0]=1;
	for(int i=1;i<=cur[0];i++){
		while(L<q[0][i].l)del(a[L++]);
		while(L>q[0][i].l)upd(a[--L]);
		while(R<q[0][i].r)upd(a[++R]);
		while(R>q[0][i].r)del(a[R--]);
		while(T<q[0][i].t)tim(i,++T);
		while(T>q[0][i].t)tim(i,T--);
		ans[q[0][i].i]=su;
	}
	for(int i=1;i<=cur[0];i++)
		cout<<ans[i]<<"\n";
	return 0;
} 	

::::

树上莫队

维护一个 dfn 序,然后按照顺序去做莫队。


https://www.luogu.com.cn/article/28abjvjt

posted @ 2026-10-12 11:56  Ivan422  阅读(4)  评论(0)    收藏  举报