[数据结构] 莫队

莫队是一种基于分块思想的离线算法。

本文章介绍初等莫队。

今天才学会带修

普通莫队

[国家集训队]小Z的袜子

一个板子题,要求维护平方和。

直接给出公式吧:

\(ans=\frac{\sum_{i=1}^c x_i^2-(R-L+1)}{(R-L+1)(R-L)}\)

莫队的基本思想

对左右端点进行分块,左端点所在块为第一关键字,右端点所在块为第二关键字,这样就保证了普通莫队的复杂度是 \(\text O(n\sqrt{n})\) .

初始时 \(l=1,r=0\),意义是 \([l,r]\) 这段区间已经处理过了,然后根据询问的左右端点不断扩展或者撤销。(具体用四个循环实现)

细节部分

  • 特判 \(l=r\) 的情况。

  • 防止死循环。

  • 一定注意左右端点 \(l,r\) 的把控,细节想到位。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
	T x=0;char ch=getchar();bool fl=false;
	while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
	}
	return fl?-x:x;
}
const int maxn = 5e4 + 10;
#define LL long long
int c[maxn],t[maxn];
struct node{
	int l,r,id;
	LL a,b;
}a[maxn];
LL gcd(LL a,LL b){
	if(!b)return a;
	return gcd(b,a%b);
}
int block[maxn],n1,n,m;
#define read() read<int>()
#include <cmath>
inline bool cmp(node a,node b){
	if(block[a.l]!=block[b.l])return a.l<b.l;
	return a.r<b.r;
}
inline bool cmp_id(node a,node b){
	return a.id<b.id;
}
LL ans;
inline void update(int p,int val){
	ans-=t[c[p]]*t[c[p]];
	t[c[p]]+=val;
	ans+=(LL)t[c[p]]*t[c[p]];
}
void solve(){
	for(int i=1,l=1,r=0;i<=m;i++){
		while(r<a[i].r)update(r+1,1),r++;
		while(l>a[i].l)update(l-1,1),l--;
		while(r>a[i].r)update(r,-1),r--;
		while(l<a[i].l)update(l,-1),l++;
		if(a[i].l==a[i].r){
			a[i].a=0;a[i].b=1;continue;
		}
		a[i].a=ans-(a[i].r-a[i].l+1);
		a[i].b=1LL*(a[i].r-a[i].l+1)*(a[i].r-a[i].l);
		LL g=gcd(a[i].a,a[i].b);
		a[i].a/=g;a[i].b/=g;
	}
}
int main(){
	n=read();m=read();n1=(int)sqrt(n+0.5);
	for(int i=1;i<=n;i++)c[i]=read();
	for(int i=1;i<=n;i++)block[i]=(i-1)/n1+1;
	for(int i=1;i<=m;i++){
		a[i].l=read();a[i].r=read();a[i].id=i;
	}
	sort(a+1,a+1+m,cmp);
	solve();
	sort(a+1,a+1+m,cmp_id);
	for(int i=1;i<=m;i++)printf("%lld/%lld\n",a[i].a,a[i].b);
	return 0;
}

带修改莫队

[国家集训队]数颜色 / 维护队列

与普通莫队不同的是,带修改莫队会 在一个特定的时间点进行对信息的修改 ,这就需要我们把二元组改为三元组 \((l,r,time)\)

其中时间按照第三关键字排序,块长改为 \(n^{\frac{2}{3}}\)

复杂度为 \(\text O(n^{\frac{5}{3}})\)

做法和普通莫队的区别:

每次扩展区间的时候(不要以为时间是顺序的),看当前需要修改的操作,如果这个操作在时间轴中包含于扩展的区间 \([l,r]\) ,看它和当前查询操作 \(time\) 的关系,进行合理的 撤销或修改

需要自己慢慢体会。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
	T x=0;char ch=getchar();bool fl=false;
	while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
	}
	return fl?-x:x;
}
const int maxn = 1333333 + 10;
#include <cmath>
int n,m,c[maxn],ct[maxn];
int mem[maxn][3],c1,c2,tot[maxn];
int Ans[maxn],n1;
struct node{
	int l,r,t,id;
	bool operator <(const node &x)const{
		if(l/n1 == x.l/n1){
			if(r/n1 == x.r/n1)
				return t<x.t;
			return r<x.r;
		}
		return l<x.l;
	}
}a[maxn];
int ans;
#define read() read<int>()
inline void del(int x){
	if(--tot[x]==0)ans--;
}
inline void add(int x){
	if(++tot[x]==1)ans++;
}
int main(){
	n=read();m=read();n1=pow(n,0.666666);
	for(int i=1;i<=n;i++){
		c[i]=read();ct[i]=c[i];
	}
	for(int i=1;i<=m;i++){
		char op;cin>>op;int x=read(),b=read();
		if(op=='Q'){
			c1++;a[c1]=(node){x,b,c2,c1};
		}
		else {
			c2++;mem[c2][0]=x;mem[c2][1]=ct[x];mem[c2][2]=ct[x]=b;//鏇存敼浣嶇疆锛屽師鏉ワ紝鐜板湪棰滆壊
		}
	}
	sort(a+1,a+1+c1);
	int l=1,r=0,last=1;
	for(int i=1;i<=c1;i++){
		while(last<=a[i].t){
			if(l<=mem[last][0] && mem[last][0]<=r)
				del(mem[last][1]),add(mem[last][2]);
			c[mem[last][0]]=mem[last][2];
			last++;
		}
		while(last-1>a[i].t){
			if(l<=mem[last-1][0] && mem[last-1][0]<=r)
				del(mem[last-1][2]),add(mem[last-1][1]);
			c[mem[last-1][0]]=mem[last-1][1];
			last--;
		}
		while(r<a[i].r)add(c[r+1]),r++;
		while(l>a[i].l)add(c[l-1]),l--;
		while(r>a[i].r)del(c[r]),r--;
		while(l<a[i].l)del(c[l]),l++;
		Ans[a[i].id]=ans;
	}
	for(int i=1;i<=c1;i++)printf("%d\n",Ans[i]);
	return 0;
}