优秀数据结构2--莫队
众所周知,欣隆妹妹是个毒瘤。
经常出毒瘤数据结构题。
出得挺多的是莫队。
所以今天我们来聊聊莫队

可爱的由乃。
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

浙公网安备 33010602011771号