莫队专题

莫队的使用范围

莫队是一种暴力的区间查询的方法。复杂度为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]);
}





  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值