莫队学习笔记

  

  莫队是针对区间操作问题的一种算法,利用一种近乎QJ(骗分神技)测试点的剪枝方法,强行使程序的时间复杂度降一个根号。

  

  算法基础:

    1、分块:分块是莫队的来源,利用分块思想,可以将询问分块,达到缩短查询区间,降低时间复杂度的目的。

    2、结构体关键字排序:莫队的精髓所在,通过对询问区间排序,可以减少指针的移动总距离,使总复杂度较为优秀。

    3、单调指针的简单应用:莫队的重要工具,通过两个指针的扫描,可以由前一区间转移到该区间,统计答案。

    4、离散化:数据范围极大时能起到巨大的优化作用,缩小总区间,使指针的移动更加快捷高效。

    5、卡常技巧:哎嘿嘿~~(其实莫队本质上是个暴力QwQ)。

  

  算法实现: 

    首先我们来看一个例题:

题目描述

image

输入格式

image

输出格式

image

样例

样例输入

3 4
1 2 2
1 2 1 3
1 2 1 1
1 3 1 3
2 3 2 3

样例输出

2 2
1 1
3 2
2 1
 

数据范围与提示

N=100000,M=1000000

 
 
   观察题面,我们发现,这不过是一道区间操作问题。
  首先考虑暴力思路,对于每一个询问,直接扫描对应区间即可。但是$O(nm)$的复杂度证明这肯定不是正解qwq。
   那我们考虑优化,因为该题不需要修改,所以任意区间的答案相当于是固定的。
  那我们可不可以用我们已知答案的区间,来更新出我们询问的区间呢?
  答案是可以的,这也就是莫队算法的由来。
  考虑如何更新新的区间,可以想到用两个树状数组维护每个区间中在[l,r]中的数值,及不同的权值数量,每次将指针移动,更新状态即可。
#include<bits/stdc++.h>
#define re register
#define lowbit(x) ((x)&(-(x)))
using namespace std;
int n,m,bel[1000005],size,tot,data[1000005];
int tr1[4000005],tr2[4000005],cnt[1000005];
int la[1000005],ra[1000005];
struct node{int l,r,a,b,id,la,ra;}q[1000005];
inline bool cmp(node a,node b)
{return (bel[a.l]^bel[b.l])?bel[a.l]<bel[b.l]:((bel[a.l]&1)?a.r<b.r:a.r>b.r);}
inline int read(){
    re int a=0,b=1;re char ch=getchar();
    while(ch<'0'||ch>'9')
        b=(ch=='-')?-1:1,ch=getchar();
    while(ch>='0'&&ch<='9')
        a=(a<<3)+(a<<1)+(ch^48),ch=getchar();
    return a*b;
}
inline void add1(re int x,re int y){
    for(;x<=m;x+=lowbit(x)) tr1[x]+=y;
}
inline int getsum1(re int x){
    re int res=0;
    for(;x;x-=lowbit(x)) res+=tr1[x];
    return res;
}
inline void add2(re int x,re int y){
    for(;x<=m;x+=lowbit(x)) tr2[x]+=y;
}
inline int getsum2(re int x){
    re int res=0;
    for(;x;x-=lowbit(x)) res+=tr2[x];
    return res;
}
inline void inc(re int x){
    if(++cnt[x]==1)add2(x,1);add1(x,1);
}
inline void del(re int x){
    if(--cnt[x]==0)add2(x,-1);add1(x,-1);
}
signed main(){
    n=read(),m=read();
    size=sqrt(n);
    tot=(n+size-1)/size;
    for(re int i=1;i<=tot;i++)
        for(re int j=(i-1)*size+1;j<=i*size;j++)
            bel[j]=i;
    for(re int i=1;i<=n;i++) data[i]=read();
    for(re int i=1;i<=m;i++) 
        q[i].l=read(),q[i].r=read(),q[i].a=read(),q[i].b=read(),q[i].id=i;
    sort(q+1,q+m+1,cmp);
    re int l=q[1].l,r=q[1].r;
    for(re int i=l;i<=r;i++) inc(data[i]);
    la[q[1].id]=getsum1(q[1].b)-getsum1(q[1].a-1);
    ra[q[1].id]=getsum2(q[1].b)-getsum2(q[1].a-1);
    for(re int i=2;i<=m;i++){
        while(l<q[i].l)del(data[l++]);
        while(l>q[i].l)inc(data[--l]);
        while(r<q[i].r)inc(data[++r]);
        while(r>q[i].r)del(data[r--]);
        la[q[i].id]=getsum1(q[i].b)-getsum1(q[i].a-1);
        ra[q[i].id]=getsum2(q[i].b)-getsum2(q[i].a-1);
    }
    for(re int i=1;i<=m;i++)
        printf("%d %d\n",la[i],ra[i]);
    return 0;
}
Ahoi 作业

 

   此,我们可以把莫队算法理解为一种优雅的暴力,只不过它的剪枝极为巧妙,达到了理想的效果。

  
  以上仅为静态莫队,相应的还有带修莫队、树上莫队、回滚莫队、二维莫队等,在这里先咕掉了。。。(溜...
 
 
posted @ 2019-10-12 11:21  Hzoi-lyl  阅读(181)  评论(1编辑  收藏  举报