众所周知,欣隆妹妹是个毒瘤。

经常出毒瘤数据结构题。


出得挺多的是莫队。

所以今天我们来聊聊莫队

可爱的由乃。

part one - 什么是莫队?

。。。

part two - 莫队的原理与实现:

第一步,让询问离线。

你将每一个询问放进一个结构体,然后对询问进行分块,每一个询问对应的块记为bel,目前,块大小最优为n/sqrt(m*2/3)

如何排序让时间复杂度正确???主流的做法是,第一关键字为左端点所在的块,如果同块则比右端点

你还可以尝试如果左端点所在块为奇数则a.r>b.r否则a.r<b.r,这样可能比较块,但也挺玄学

莫队需要两个指针,每次移动两个指针,让他对应到当前的l,r,每一次删除或加入时更新答案

part three 莫队基础题:

1.小z的袜子:

求区间内取到相同颜色袜子的概率(差不多就是方案数

设每一种颜色在区间中出现的次数为c[i],那么对答案的贡献就是c[i]*(c[i]-1)/2

然后每一次删除,插入,更改c[i],重新算一遍贡献,然后减去对但减去原来的贡献加上新的贡献

code::(贡献一波手码的排序)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define Rint register int
#define Temp template<typename T>
Temp inline void read(T &x) {
	x=0;T w=1,ch=getchar();
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	x*=w;
}
const int maxn=5e5+10;
int a[maxn],t[maxn],f[maxn],res[maxn],x[maxn],y[maxn],bh[maxn];
int n,m,left=1,right=1,p;
long long ans=0;
inline int gcd(int n,int m) {
	if(n%m==0) return m;
	return gcd(m,n%m);
}
inline void write(long long x) {
	if(x>=10) write(x/10);
	putchar(x%10+'0');
}
inline void qsort(int l,int r) {
	int i=l,j=r,mid=(l+r)>>1,tl=x[(l+r)>>1],tr=y[(l+r)>>1];
	do{
		if((i/p)==(j/p)) {
			while(x[i]<tl) ++i;
			while(tl<x[j]) --j;
			if(i<=j) {
				std::swap(x[i],x[j]);
				std::swap(y[i],y[j]);
				std::swap(bh[i],bh[j]);
				++i;
				--j;
			}
		}
		else {
			while(y[i]<tr) ++i;
			while(tr<y[j]) --j;
			if(i<=j) {
				std::swap(x[i],x[j]);
				std::swap(y[i],y[j]);
				std::swap(bh[i],bh[j]);
				++i;
				--j;
			}
		}
	}while(i<=j);
	if(l<j) qsort(l,j);
	if(i<r) qsort(i,r);
}
int main() {
	read(n);read(m);
	p=sqrt(n);
	for (Rint i=1;i<=n;++i) {
		read(a[i]);
	}
	for (Rint i=1;i<=m;++i) {
		read(x[i]);read(y[i]);bh[i]=i;
	}
	qsort(1,m);
	t[a[1]]++;
	for (Rint i=1;i<=m;++i) {
		int ll=x[i];
		int rr=y[i];
		while(left>ll) {
			--left;
			ans+=t[a[left]];
			t[a[left]]++;
		}
		while(right<rr) {
			++right;
			ans+=t[a[right]];
			t[a[right]]++;
		}
		while(left<ll) {
			t[a[left]]--;
			ans-=t[a[left]];
			++left;
		}
		while(right>rr) {
			t[a[right]]--;
			ans-=t[a[right]];
			--right;
		}
		f[bh[i]]=ans;
		res[bh[i]]=right-left+1;
	}
	for (Rint i=1;i<=m;++i) {
		if(f[i]==0) {
			putchar('0');putchar('/');putchar('1');putchar('\n');
		}
		else {
			long long tot=res[i];
			long long c=gcd(f[i],tot*(tot-1)>>1);
			write(f[i]/c);putchar('/');write(tot*(tot-1)/c/2);putchar('\n');
		}
	}
	return 0;
}

2.HH的项链

莫队,统计出现次数。

每一次插入,当前a[i]的出现次数+1,如果a[i]的出现次数为1,答案+1

每一次删除,当前a[i]的出现次数-1,如果a[i]的出现次数为0,答案-1

题太菜就没code了

3.采花

莫队,还是统计出现次数

每一次插入,当前a[i]的出现次数+1,如果a[i]的出现次数为2,答案+1

每一次删除,当前a[i]的出现次数-1,如果a[i]的出现次数为1,答案-1

code::(为啥要给的来着

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
struct query{
	int l,r,id;
}q[300005];
int num[300005],Ans[300005],n,m,a[300005],bel[300005],sz,ans=0,c;
int read() {
	register int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*w;
}
bool cmp(query a,query b) {
	if(bel[a.l]!=bel[b.l]) return a.l<b.l;
	else if(bel[a.l]&1) return a.r>b.r;
	else return a.r<b.r;
}
void ins(int x) {
	++num[a[x]];
	if(num[a[x]]==2) ++ans;
}
void del(int x) {
	--num[a[x]];
	if(num[a[x]]==1) --ans;
}
int main() {
	n=read();c=read();m=read();
	sz=m/sqrt(n*2/3);
	for (int i=1;i<=n;++i) a[i]=read();
	for (int i=1;i<=n;++i) bel[i]=(i-1)/sz+1;
	for (int i=1;i<=m;++i){
		q[i].l=read();q[i].r=read();q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	int l=1,r=0;
	for (int i=1;i<=m;++i) {
		while(l<q[i].l) del(l++);
		while(l>q[i].l) ins(--l);
		while(r<q[i].r) ins(++r);
		while(r>q[i].r) del(r--);
		Ans[q[i].id]=ans;
	}
	for (int i=1;i<=m;++i) printf("%d\n",Ans[i]);
	return 0;
}

part five:Ynoi

1.这是我自己的发明

换根,就是分类讨论以下,其实就是dfs序上的两个区间

所以就成了不换根

如果颜色出现次数>sqrt(n)暴力

否则,就是类似二维数点。

因为修改O(m)个,查询O(nsqrt(n))个,考虑用分块代替树状数组,这样可以根号平衡

2.跳进兔子洞

莫队+bitset

3.此时此刻的光辉

因为质数个数少,所以根号分治

前<sqrt(n)的质数暴力,后面的莫队维护

所以时间复杂度是对的

end.

计划补充的内容:

1.回滚莫队

2.二次离线莫队

orz nzhtl1477

01-12 17:36