分块,莫队算法总结

Posted zztzz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分块,莫队算法总结相关的知识,希望对你有一定的参考价值。

分块算法总结

  分块,就是一种暴力算法,不过复杂度优于暴力,是基于足够的预处理和合理可行的维护操作进行优化时间,

 在预处理+维护的耗时上与暴力处理的耗时上找到一种平衡,于是出了这个优美的算法

  标志:查询某一区间内元素种类数,查询某一区间大于等于某一元素的数的个数(即排名

 模板:LuoguP2801 教主的魔法

  为了查询大于等于C的个数,可以排序,用区间长度-C的排名就是 答案数。

  所以可以动态维护一个块内有序的数组

 

#include<bits/stdc++.h>
#define re register
#define inc(i,j,k) for(re int i=j;i<=k;++i)
#define il inline 
using namespace std;
const int maxn=1000010;
vector <int> ve[1010];
int v[maxn],bl[maxn],atag[maxn];
int n,m,base;
il void reset(int x) //重新维护一个块内元素的顺序
{
    ve[x].clear();
    inc(i,(x-1)*base+1,min(x*base,n)) ve[x].push_back(v[i]);//别忘了容易溢出n的数组大小呀
    sort(ve[x].begin(),ve[x].end());
}
il void add(int a,int b,int c)//区间加
{
    inc(i,a,min(bl[a]*base,b)) v[i]+=c;//暴力处理前半部分
    reset(bl[a]);
    if(bl[a]!=bl[b]) {inc(i,(bl[b]-1)*base+1,b) v[i]+=c; reset(bl[b]);}//暴力处理后面部分
    inc(i,bl[a]+1,bl[b]-1) atag[i]+=c;
}
il int query(int a,int b,int c)//区间查询
{
    int ans=0;
    inc(i,a,min(bl[a]*base,b)) if(v[i]+atag[bl[a]]<c) ans++; //暴力处理前部分
    if(bl[a]!=bl[b])
    {
        inc(i,(bl[b]-1)*base+1,b) if(v[i]+atag[bl[b]]<c) ans++;//暴力处理后面部分
    }
    inc(i,bl[a]+1,bl[b]-1)//对于块来说,直接减去lazy_tag的影响
    {
        int x=c-atag[i];
        ans+=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin();//查询第一个大于等于x的数的位置
    }
    return ans;
}
int main()
{
    scanf("%d %d",&n,&m);
    base=sqrt(n);
    inc(i,1,n) scanf("%d",&v[i]),bl[i]=(i-1)/base+1,ve[bl[i]].push_back(v[i]);
    inc(i,1,bl[n]) sort(ve[i].begin(),ve[i].end());
    char s;
    re int x,y,z;
    inc(i,1,m)
    {
        s=getchar();
        while(s!=A&&s!=M) s=getchar();
        if(s==M)
        {
            scanf("%d %d %d",&x,&y,&z);
            add(x,y,z);
        }
        else 
        {
            scanf("%d %d %d",&x,&y,&z);
            printf("%d
",y-x+1-query(x,y,z));
        }
    }
}

 

其他例题先咕着吧

 

然后...

莫队

莫队其实也是基于分块的算法,主要是一种思想吧我认为,基于对询问问题的离线处理按一定顺序去处理问题

然后保证了暴力的复杂度

具体实现是要按左区间的块排序,快相同的按右端点排序,保证同一个块内复杂度O(n),共有sqrt(n)块

所以总复杂度 O(n*sqrt(n))

主要用于查询元素的种类数

模板:LuoguP2709 小B的询问

#include<bits/stdc++.h>
#define ll long long
#define re register
using namespace std;
#define maxn 50005
struct query
{
    int l,r,id,pos;
//    friend bool operator < (query xx,query yy)
//    {    if(xx.pos==yy.pos) return xx.l<yy.r;    else return xx.pos<yy.pos;} //这里是按奇偶性排序,可以先忽略
    bool operator <(const query &x) const {if(pos==x.pos)return r<x.r;
   else return pos<x.pos;} 
}a[maxn];
int b[maxn],n,m,K;ll cnt[maxn],Ans[maxn];
inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch==-)f=-1;}while(ch<0||ch>9);
    do{x=x*10+ch-0;ch=getchar();}while(ch>=0&&ch<=9);
    return x*f;
}
int main()
{
//    freopen("testdata.in.txt","r",stdin);
//    freopen("4321.txt","w",stdout);
    n=read(); m=read(); K=read();
    int siz=(int) sqrt(n);
    for(re int i=1;i<=n;++i) b[i]=read();
    for(re int i=1;i<=m;++i)
    {
        a[i].l=read(); a[i].r=read(); a[i].id=i;
        a[i].pos=(a[i].l-1)/siz+1;
    }
    sort(a+1,a+m+1);
    int l=1,r=0; long long ans=0;
    for(re int i=1;i<=m;++i)
    {
        while(l>a[i].l) { l--; cnt[b[l]]++; ans+= 2*cnt[b[l]]-1 ;} //外拓的时候,先加减再统计
        while(r<a[i].r) { r++; cnt[b[r]]++; ans+= 2*cnt[b[r]]-1 ;}
        while(r>a[i].r) {cnt[b[r]]--; ans-= 2*cnt[b[r]]+1 ; r--; } //回溯的时候,先统计再加减
        while(l<a[i].l) {cnt[b[l]]--; ans-= 2*cnt[b[l]]+1 ; l++; }
        Ans[a[i].id]=ans;
    }
    for(re int i=1;i<=m;++i) printf("%lld
",Ans[i]);
}

其次就是对于莫队的修改操作了,带修莫队与原版的差异是多了个时间轴

把修改操作看作删除旧的+增加新的,这种思想很多时候都用的上

也是裸暴力,只不过多关注时间轴上修改都干了什么,这样查询的时候只考虑当前时间之前的操作就好了

分块大小为 (n*t)1/3  复杂度O((n4*t)1/3)   ->不会证

模板题 LuoguP1903 数颜色

//窝洛谷突然看不了自己提交的代码了。。。先复制第一篇题解的把,,等哪天我想起来再更(逃

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define fo(a,b,c) for(int a=b;a<=c;a++)
#define go(a,b,c) for(int a=b;a>=c;a--)
int read(){
    int a=0,f=0;char c=getchar();
    for(;c<0||c>9;c=getchar())if(c==-)f=1;
    for(;c>=0&&c<=9;c=getchar())a=a*10+c-0;
    return f?-a:a;
}
const int N=10001;
int a[N],p[1000001],ans[N],divi;
struct nod{int pla,pre,suc;}cg[N];
struct node{int l,r,t,bel;}ls[N];
int cmp(node a,node b){
    if(a.l/divi!=b.l/divi)return a.l/divi<b.l/divi;
    if(a.r/divi!=b.r/divi)return a.r/divi<b.r/divi;
    return a.t<b.t; 
}
int main(){
    int n=read(),m=read(),ln=0,tn=0,l=1,r=0,t=0,num=0;
    fo(i,1,n)a[i]=read();
    fo(i,1,m){
        scanf("
");
        if(getchar()==R){//如果读入修改则记录修改的地点,修改前的数字和修改后的数字
            ++tn;cg[tn].pla=read(),cg[tn].suc=read();
            cg[tn].pre=a[cg[tn].pla];
            a[cg[tn].pla]=cg[tn].suc;
        } 
        else ls[++ln]=(node){read(),read(),tn,ln};
    }
    divi=ceil(exp((log(n)+log(tn))/3));//分块大小
    go(i,tn,1)a[cg[i].pla]=cg[i].pre;
    sort(ls+1,ls+ln+1,cmp);
    fo(i,1,m){
        while(ls[i].l<l)num+=!p[a[--l]]++;
        while(ls[i].l>l)num-=!--p[a[l++]];//l移动
        while(ls[i].r>r)num+=!p[a[++r]]++;
        while(ls[i].r<r)num-=!--p[a[r--]];//r移动
        while(ls[i].t<t){
            int pla=cg[t].pla;
            if(l<=pla&&pla<=r)num-=!--p[a[pla]];
            a[pla]=cg[t--].pre;
            if(l<=pla&&pla<=r)num+=!p[a[pla]]++;
        };
        while(ls[i].t>t){
            int pla=cg[++t].pla;
            if(l<=pla&&pla<=r)num-=!--p[a[pla]];
            a[pla]=cg[t].suc;
            if(l<=pla&&pla<=r)num+=!p[a[pla]]++;
        };//t移动
        ans[ls[i].bel]=num;
    }
    fo(i,1,ln)printf("%d
",ans[i]); 
    return 0;
}

二次离线莫队

updataing......

 

以上是关于分块,莫队算法总结的主要内容,如果未能解决你的问题,请参考以下文章

高级数据结构分块莫队笛卡尔树算法模板几题

高级数据结构分块莫队笛卡尔树算法模板几题

莫队算法&#183;初探总结

数列分块总结(遴选hzwer分块九题及其他题目)(分块)

[学习-思考-探究]莫队算法 曼哈顿最小生成树与分块区间询问算法

XOR and Favorite Number(莫队算法+分块)