介绍
莫队由莫涛发明,是一种处理区间询问的离线算法。
莫队
用两个指针 l , r l,r l,r来维护区间 [ l , r ] [l,r] [l,r]内的答案。
使用分块,设每块大小为 b b b,则有 n b \dfrac nb bn块。将询问离线下来,以 l l l所在块为第一关键字, r r r为第二关键字来排序。
- 对于每一个块, r r r是递增的,最多移动 n n n次,则总共移动 n 2 b \dfrac{n^2}{b} bn2次
- l l l在同一块内单次移动最多移动 b b b次,总共移动 m b mb mb次
- 跨快移动, l l l最多移动 2 b 2b 2b次, r r r最多移动 n n n次,最多跨块 n b \dfrac nb bn次,总共移动 ( n 2 b + 2 n ) (\dfrac{n^2}{b}+2n) (bn2+2n)次
三部分加起来,时间复杂度为 O ( n 2 b + m b ) O(\dfrac{n^2}{b}+mb) O(bn2+mb)。当 b = n m b=\dfrac{n}{\sqrt m} b=mn时最优,为 O ( n m ) O(n\sqrt m) O(nm)。
一般情况下,令 b = n b=\sqrt n b=n,时间复杂度为 O ( ( m + n ) n ) O((m+n)\sqrt n) O((m+n)n)。
code
struct node{
int l,r,id;
}w[N];
bool cmp(node ax,node bx){
int a1=ax.l/bl,b1=bx.l/bl; //bl为块数
if(a1!=b1) return a1<b1;
return ax.r<bx.r;
}
void add(int x){
}
void dele(int x){
}
void solve(){
sort(w+1,w+n+1,cmp);
int l=1,r=0;
for(int i=1;i<=n;i++){
int x=w[i].l,y=w[i].r;
while(l>x) add(--l);
while(l<x) dele(l++);
while(r>y) dele(r--);
while(r<y) add(++r);
ans[w[i].id]=now;
}
}
注意
while(l>x) add(--l);
while(l<x) dele(l++);
while(r>y) dele(r--);
while(r<y) add(++r);
因为有删除操作,所以在 l > r + 1 l>r+1 l>r+1的情况下会出现删除了没有加入的数的情况。
如果用 s e t set set来维护,则会出问题,所以可以改为
while(l>x) add(--l);
while(r<y) add(++r);
while(l<x) dele(l++);
while(r>y) dele(r--);
奇偶排序
若用奇偶排序,代码会跑得快一些。
bool cmp(node ax,node bx){
int a1=ax.l/bl,b1=bx.l/bl;
if(a1!=b1) return a1<b1;
if(a1&1) return ax.r<bx.r;
return ax.r>bx.r;
}
例题
用桶来存每种颜色的袜子,求出抽出颜色相同的袜子的种数,再除以抽出任意两只袜子的种数即可。
code
#include<bits/stdc++.h>
using namespace std;
int n,m,bl;
long long re,c[50005],z[50005],ans[50005],v[50005];
struct node{
int l,r,id;
}a[50005];
bool cmp(node ax,node bx){
int a1=ax.l/bl,b1=bx.l/bl;
if(a1!=b1) return a1<b1;
return ax.r<bx.r;
}
void add(int x){
re-=z[x]*(z[x]-1);
++z[x];
re+=z[x]*(z[x]-1);
}
void dele(int x){
re-=z[x]*(z[x]-1);
--z[x];
re+=z[x]*(z[x]-1);
}
void gcd(long long i,long long j){
long long p=i,q=j;
while(j>0){
i%=j;swap(i,j);
}
printf("%lld/%lld\n",p/i,q/i);
}
int main()
{
scanf("%d%d",&n,&m);
bl=sqrt(n);
for(int i=1;i<=n;i++){
scanf("%lld",&c[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&a[i].l,&a[i].r);
a[i].id=i;
long long k=a[i].r-a[i].l+1;
k=k*(k-1);
v[i]=k;
}
sort(a+1,a+m+1,cmp);
for(int k=1,i=0,j=1;k<=m;k++){
int l=a[k].l,r=a[k].r;
while(i<r) add(c[++i]);
while(i>r) dele(c[i--]);
while(j<l) dele(c[j++]);
while(j>l) add(c[--j]);
ans[a[k].id]=re;
}
for(int i=1;i<=m;i++){
if(ans[i]==0){
printf("0/1\n");
continue;
}
gcd(ans[i],v[i]);
}
return 0;
}
参考博客:https://blog.youkuaiyun.com/KonjakuLAF/article/details/126387244?spm=1001.2014.3001.5502