莫队的使用范围
莫队是一种暴力的区间查询的方法。复杂度为n*sqrt(n);
以下面一题为例
画
题目描述
墙上挂上了好多画!
机房后面的墙上整齐地挂上了一排n副画,画有许多种类,分别用1,2,3...表示。
但是有许多画都重复了,现在想要知道在一些区间中有多少副不同种类的画;
输入
第一行包括两个整数n和m,表示画的数量与询问的数量。
接下来一行n个数A[1],A[2],A[3]...A[n]。第i个数表示第i副画的种类为A[i]。
接下来m行,每行两个整数L,R表示询问的区间。
输出
输出m行,每行一个整数表示每个询问中该区间内不同的种类的画的个数。
样例输入
5 3
1 3 1 3 2
1 3
2 5
1 5
样例输出
2
3
3
提示
n<=50000,m<=50000,A[i]<=1,000,000,000
我们发现这样的题目如果使用线段树或其他的数据结构是难以解决问题的因为要保存的信息比较特殊
对于一个区间[l,r]和区间[l,r+1],[l-1,r]我们发现其转移的复杂度是O(1)的那么,我们可以离线。
得到代码
for(int i=2;i<=m;i++){
while(l<B[i].x){
remove(l);l++;
}
while(B[i].x<l){
l--;add(l);
}
while(r<B[i].y){
r++;add(r);
}
while(B[i].y<r){
remove(r);r--;
}ans[B[i].id]=res;
}
可是如何使复杂度最优呢?
如果我们按题目顺序来复杂度还是O(n*n)
此时我们可以分块
对于x在[a,b]中的元素我们使y从小到大排于是复杂度为O(n);即把y从最小到最大的遍历一次
我们去一个S=sqrt(n)于是复杂度就成了O(n*sqrt(n))
但是我们发现每次遍历完y后R就非常大了,而我们却又将y从小开始遍历显然不优
于是我们将块的编号为奇数的y从小到大排,块的编号为偶数的y从大到小排最做到了优化
const int M=50005;
int A[M],n,m,S,F[M],cnt[M],ans[M],res;
inline void Rd(int &res){
char c;res=0;
while(c=getchar(),!isdigit(c));
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),isdigit(c));
}
struct node{
int x,y,id;
void rd(){
Rd(x),Rd(y);
}
bool operator<(const node& A)const{
if(A.x/S!=x/S)return x/S<A.x/S;
if(x/S&1)return y<A.y;
return y>A.y;
}
}B[M];
inline void add(int x){
x=A[x];
if(!cnt[x])res++;
cnt[x]++;
}
inline void remove(int x){
x=A[x];
cnt[x]--;
if(!cnt[x])res--;
}
int main(){
Rd(n);Rd(m);
S=sqrt(m);
for(int i=1;i<=n;i++){
Rd(A[i]);
F[i]=A[i];
}sort(F+1,F+1+n);
int k=unique(F+1,F+1+n)-F-1;
for(int i=1;i<=n;i++)
A[i]=lower_bound(F+1,F+1+k,A[i])-F;
for(int i=1;i<=m;i++){
B[i].rd();B[i].id=i;
}
sort(B+1,B+1+m);
int l=B[1].x,r=B[1].y;
for(int i=l;i<=r;i++){
if(!cnt[A[i]])res++;
cnt[A[i]]++;
}ans[B[1].id]=res;
for(int i=2;i<=m;i++){
while(l<B[i].x){
remove(l);l++;
}
while(B[i].x<l){
l--;add(l);
}
while(r<B[i].y){
r++;add(r);
}
while(B[i].y<r){
remove(r);r--;
}ans[B[i].id]=res;
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
}