~~推荐播客~~
「分块」数列分块入门1 – 9 by hzwer
浅谈基础根号算法——分块
博主蒟蒻,有缘人可直接观摩以上大佬的博客。。。
#6277. 数列分块入门 1
题目大意:
给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及区间加法,单点查值。
分块入门,区间修改+单点查询
所谓分块,实际上是一种优美的暴力,算是一种数据结构吧。。。
将区间分成许多大小相同的块,对于多出来的部分暴力去做,一般块的大小为$√n$
看了大佬们的代码,有点儿懵逼=_=
来模拟一下流程吧,可能会清晰一点儿
如图所示,暴力地将一段长度为$10$的序列分成了$4$块,其中$3$块是大小为$√n$的块。
每个数所属块的标号显然是$bl[i]=(i-1)/blo+1$,其中$blo$为块的大小(手推一下即可)
假如你暴力修改某个区间,假设为$[2,7]$,(算了,自己暴力模拟吧)
你首先要做的是暴力修改小区间,此时小区间为$[2.3]$和$[7,7]$
这里小区间一般有两个,即最左边不在整个块中的数,和最右边不在整个块中的数
手推一下,暴力修改的左边区间为$[l,min(bl[l]*blo],r)$
emmmmm,我知道暴力修改左边区间的右端点就是$bl[l]*blo]$,为什么还要和给定修改的区间右端点去$min$呢?
。。。思考一下?(大佬说显然嘛)
有可能这个待修改区间$[l,r]$就在一个块中。
暴力修改的右区间左短点为$(bl[r]-1)*blo+1$
在这之前要特判一下$bl[l]==bl[r]$,也是防止待修改区间$[l,r]$在一个块中的情况
最后,当然是对完整的块的修改,范围是$bl[l+1],bl[r]-1$,这时修改的是区间加法标记
#include<bits/stdc++.h> #define N 1010100 using namespace std; int n,a[N],blo[N],atag[N]; void update(int l,int r,int c){ for(int i=l;i<=min(blo[l]*blo[0],r);i++) a[i]+=c; if(blo[l]!=blo[r]) for(int i=(blo[r]-1)*blo[0]+1;i<=r;i++) a[i]+=c; for(int i=blo[l]+1;i<=blo[r]-1;i++) atag[i]+=c; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); blo[0]=sqrt(n); for(int i=1;i<=n;i++) blo[i]=(i-1)/blo[0]+1;//每一个数所在的块 for(int i=1;i<=n;i++){ int opt,l,r,c; scanf("%d%d%d%d",&opt,&l,&r,&c); if(!opt) update(l,r,c); else printf("%d ",a[r]+atag[blo[r]]); }return 0; }
#6278. 数列分块入门 2
给出一个长为 $n$的数列,以及 $n$ 个操作,操作涉及区间加法,询问区间内小于某个值 $x$ 的元素个数。
跟随大佬的思路,既然一个题你要考虑用分块去做,那么你要考虑以下$3$项
1.不完整的块如何处理?
2.整块如何处理?
3.预处理什么信息?
此题关键在于查找区间内小于$x$的元素个数,倘若这个区间不是有序的,难道要暴力查询吗?(暴力只能针对于不完整的块)
那么就必须让他完整的块变得有序,然后二分查找这个快里$<x$的数的个数即可
修改完成之后,边上的那两个块不就部分改变了吗,怎么维护呢?
当然还是暴力,暴力清除块,在暴力添加回去。
$vector$大法好
#include<iostream> #include<cstdio> #include<cmath> #include<vector> #include<algorithm> #define N 500056 using namespace std; int bl[N],n,v[N],atag[N],blo; vector<int>V[N]; void update(int x){//第x个块 V[x].clear(); for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++) V[x].push_back(v[i]); sort(V[x].begin(),V[x].end()); } void add(int l,int r,int val){ for(int i=l;i<=min(bl[l]*blo,r);i++) v[i]+=val; update(bl[l]); if(bl[l]!=bl[r]){ for(int i=(bl[r]-1)*blo+1;i<=r;i++) v[i]+=val; update(bl[r]); } for(int i=bl[l]+1;i<=bl[r]-1;i++) atag[i]+=val; } int query(int l,int r,int c){ int ans=0; for(int i=l;i<=min(bl[l]*blo,r);i++) if(v[i]+atag[bl[l]]<c) ++ans; if(bl[l]!=bl[r]){ for(int i=(bl[r]-1)*blo+1;i<=r;i++) if(v[i]+atag[bl[r]]<c) ++ans; } for(int i=bl[l]+1;i<=bl[r]-1;i++){ int x=c-atag[i]; ans+=lower_bound(V[i].begin(),V[i].end(),x)-V[i].begin(); } return ans; } int main() { scanf("%d",&n); blo=sqrt(n); for(int i=1;i<=n;i++){ bl[i]=(i-1)/blo+1;//是除以这个块的大小 scanf("%d",&v[i]); V[bl[i]].push_back(v[i]); } for(int i=1;i<=bl[n];i++) sort(V[i].begin(),V[i].end()); for(int opt,l,r,c,i=1;i<=n;i++){ scanf("%d%d%d%d",&opt,&l,&r,&c); if(!opt) add(l,r,c); else printf("%d ",query(l,r,c*c)); } return 0; }
#6279. 数列分块入门 3
我只能说这些骚操作我从来没见过,仿佛打开了异世界的大门,(大佬们早就进去看了不知多少遍了)
题目大意:
给出一个长为 $n$的数列,以及 $n$ 个操作,操作涉及区间加法,询问区间内小于某个值 $x$ 的前驱(比其小的最大元素)。
上一道题是$vector$二分查找,反之就是$STL$,而这一道也是这样的,不过用的是$set$
hzwer(他想真实表达的东西):
可以在块内维护其它结构使其更具有拓展性,比如放一个 $set$ ,这样如果还有插入、删除元素的操作,会更加的方便。
即分块套其他数据结构,%%%%强无敌呀!!
分块的调试检测技巧:
可以生成一些大数据,然后用两份分块大小不同的代码来对拍,还可以根据运行时间尝试调整分块大小,减小常数。——hzwer
#include<iostream> #include<cstdio> #include<cmath> #include<vector> #include<set> #include<algorithm> #define N 500056 using namespace std; int bl[N],n,v[N],atag[N],blo; set<int>S[N]; void add(int l,int r,int val){ for(int i=l;i<=min(bl[l]*blo,r);i++){ S[bl[l]].erase(v[i]); v[i]+=val; S[bl[l]].insert(v[i]); } if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*blo+1;i<=r;i++){ S[bl[r]].erase(v[i]); v[i]+=val; S[bl[r]].insert(v[i]); } for(int i=bl[l]+1;i<=bl[r]-1;i++) atag[i]+=val; } int query(int l,int r,int val){ int ans=-1; for(int i=l;i<=min(bl[l]*blo,r);i++) if(v[i]+atag[bl[l]]<val) ans=max(ans,v[i]+atag[bl[l]]); if(bl[l]!=bl[r]){ for(int i=(bl[r]-1)*blo+1;i<=r;i++) if(v[i]+atag[bl[r]]<val) ans=max(ans,v[i]+atag[bl[r]]); } for(int i=bl[l]+1;i<=bl[r]-1;i++){ int x=val-atag[i]; set<int>::iterator it=S[i].lower_bound(x);//二分查找 if(it==S[i].begin()) continue; --it; ans=max(ans,*it+atag[i]); } return ans; } int main() { scanf("%d",&n); blo=sqrt(n); for(int i=1;i<=n;i++){ bl[i]=(i-1)/blo+1;//是除以这个块的大小 scanf("%d",&v[i]); S[bl[i]].insert(v[i]); } for(int opt,l,r,c,i=1;i<=n;i++){ scanf("%d%d%d%d",&opt,&l,&r,&c); if(!opt) add(l,r,c); else printf("%d ",query(l,r,c)); } return 0; }
#6280. 数列分块入门 4
给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及区间加法,区间求和。
现在看着道题,你会说,这不就是线段树裸题吗,水水水水!
当你做了分块前$3$道题目后,看到这道题,你也会说,这不是分块裸题吗,随便做。
%%%%%(为什么我都说不出来呢?)
交了5遍才A掉它,太菜啦,太菜啦。。。
有两个细节,放在代码里了。
#include<iostream> #include<cstdio> #include<cmath> #include<vector> #include<set> #include<algorithm> #define N 500056 #define LL long long using namespace std; LL bl[N],n,v[N],atag[N],blo,sum[N]; void add(LL l,LL r,LL val){ for(LL i=l;i<=min(bl[l]*blo,r);i++) v[i]+=val,sum[bl[l]]+=val;//修改不完整的块时那个块的区间总和也随之改变 if(bl[l]!=bl[r]) for(LL i=(bl[r]-1)*blo+1;i<=r;i++) v[i]+=val,sum[bl[r]]+=val; for(LL i=bl[l]+1;i<=bl[r]-1;i++) atag[i]+=val; } LL query(LL l,LL r,LL mod){ LL ans=0; for(LL i=l;i<=min(bl[l]*blo,r);i++) ans=(ans%mod+(v[i]+atag[bl[l]])%mod)%mod; if(bl[l]!=bl[r]) for(LL i=(bl[r]-1)*blo+1;i<=r;i++) ans=(ans%mod+(v[i]+atag[bl[r]])%mod)%mod; for(LL i=bl[l]+1;i<=bl[r]-1;i++) ans=(ans%mod+(sum[i]+atag[i]*blo)%mod)%mod;//标记不要误写成bl[i] return ans%mod; } int main() { scanf("%lld",&n); blo=sqrt(n); for(LL i=1;i<=n;i++){ bl[i]=(i-1)/blo+1;//是除以这个块的大小 scanf("%lld",&v[i]); sum[bl[i]]+=v[i]; } for(LL opt,l,r,c,i=1;i<=n;i++){ scanf("%lld%lld%lld%lld",&opt,&l,&r,&c); if(!opt) add(l,r,c); else printf("%lld ",query(l,r,c+1)); } return 0; }
#6281. 数列分块入门 5
给出一个长为 $n$ 的数列 $a_1ldots a_n$ ,以及 $n$ 个操作,操作涉及区间开方,区间求和。
区间开方?操作很骚啊。。。(就是不会)
不难发现,这题的修改就只有下取整开方,而一个数经过几次开方之后,它的值就会变成 0 或者 1。——hzwer
然后就可以随便搞啦。。。。(详情见代码)
#include<bits/stdc++.h> #define N 5000000 using namespace std; int n,bl[N],v[N],sum[N],blo; bool flg[N]; void change_x(int x){ if(flg[x]) return; flg[x]=1; sum[x]=0; for(int i=(x-1)*blo+1;i<=x*blo;i++){ v[i]=sqrt(v[i]); if(v[i]>1) flg[x]=0; sum[x]+=v[i]; } } void change(int l,int r){ for(int i=l;i<=min(bl[l]*blo,r);i++) sum[bl[l]]-=v[i],v[i]=sqrt(v[i]),sum[bl[l]]+=v[i]; if(bl[l]!=bl[r]){ for(int i=(bl[r]-1)*blo+1;i<=min(r,n);i++){ sum[bl[r]]-=v[i],v[i]=sqrt(v[i]),sum[bl[r]]+=v[i]; } } for(int i=bl[l]+1;i<=bl[r]-1;i++) change_x(i); } int query(int l,int r){ int ans=0; for(int i=l;i<=min(bl[l]*blo,r);i++) ans+=v[i]; if(bl[l]!=bl[r]){ for(int i=(bl[r]-1)*blo+1;i<=r;i++) ans+=v[i]; } for(int i=bl[l]+1;i<=bl[r]-1;i++) ans+=sum[i]; return ans; } int main() { scanf("%d",&n); blo=sqrt(n); for(int i=1;i<=n;i++){ scanf("%d",&v[i]); bl[i]=(i-1)/blo+1; sum[bl[i]]+=v[i]; } for(int opt,l,r,c,i=1;i<=n;i++){ scanf("%d%d%d%d",&opt,&l,&r,&c); if(!opt) change(l,r); else printf("%d ",query(l,r)); } return 0; }
P4145 上帝造题的七分钟2 / 花神游历各国
有点儿坑的地方是$l$有可能大于$r$,所以要$swap(l,r)$
#include<bits/stdc++.h> #define LL long long #define N 1000000 using namespace std; LL n,m,v[N],sum[N],bl[N],blo; bool flg[N]; void change_sqrt(LL x) { if(flg[x]) return; flg[x]=1; sum[x]=0; for(LL i=(x-1)*blo+1; i<=x*blo; i++) { v[i]=sqrt(v[i]),sum[x]+=v[i]; if(v[i]>1) flg[x]=0; } } void add(LL l, LL r) { if(l>r) swap(l,r); for(LL i=l; i<=min(bl[l]*blo,r); i++) sum[bl[l]]-=v[i],v[i]=sqrt(v[i]),sum[bl[l]]+=v[i]; if(bl[l]!=bl[r]) { for(LL i=(bl[r]-1)*blo+1; i<=r; i++) { sum[bl[r]]-=v[i],v[i]=sqrt(v[i]),sum[bl[r]]+=v[i]; } } for(LL i=bl[l]+1; i<=bl[r]-1; i++) change_sqrt(i); } inline LL query(LL l,LL r) { if(l>r) swap(l,r); LL ans=0; for(LL i=l; i<=min(bl[l]*blo,r); i++) ans+=v[i]; if(bl[l]!=bl[r]) { for(LL i=(bl[r]-1)*blo+1; i<=r; i++) ans+=v[i]; } for(LL i=bl[l]+1; i<=bl[r]-1; i++) ans+=sum[i]; return ans; } int main() { scanf("%lld",&n); blo=sqrt(n); for(LL i=1; i<=n; i++) { scanf("%lld",&v[i]); bl[i]=(i-1)/blo+1; sum[bl[i]]+=v[i]; } scanf("%lld",&m); for(LL opt,l,r,i=1; i<=m; i++) { scanf("%lld%lld%lld",&opt,&l,&r); if(!opt) add(l,r); else printf("%lld ",query(l,r)); } return 0; }
#6282. 数列分块入门 6
给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及单点插入,单点询问,数据随机生成。
暴力插入,块重构,当元一个块内素数量很多时,重新构造
$vector && pair$
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<vector> #define N 200005 using namespace std; int n,v[N],st[N],blo,m; vector<int>ve[1005]; pair<int,int>query(int x){ int t=1; while(ve[t].size()<x){ x-=ve[t].size();++t; } return make_pair(t,x-1); } void rebuild(){ int top=0; for(int i=1;i<=m;i++){ for(vector<int>::iterator j=ve[i].begin();j!=ve[i].end();j++) st[++top]=*j; ve[i].clear(); } int blo2=sqrt(top); for(int i=1;i<=top;i++) ve[(i-1)/blo2+1].push_back(st[i]); m=(top-1)/blo2+1; } void insert(int a,int b){ pair<int,int> t=query(a); ve[t.first].insert(ve[t.first].begin()+t.second,b); if(ve[t.first].size()>20*blo) rebuild(); } int main() { scanf("%d",&n);blo=sqrt(n); for(int i=1;i<=n;i++){ scanf("%d",&v[i]); ve[(i-1)/blo+1].push_back(v[i]); } m=(n-1)/blo+1;//统计一共有多少块 for(int opt,l,r,c,i=1;i<=n;i++){ scanf("%d%d%d%d",&opt,&l,&r,&c); if(!opt) insert(l,r); else { pair<int,int>t=query(r); printf("%d ",ve[t.first][t.second]); } } return 0; }
#6283. 数列分块入门 7
区间乘法+区间加法+单点查询
修改时,对边上的块直接下放标记,乘先加后
#include<bits/stdc++.h> #define N 200005 #define mod 10007 using namespace std; int n,v[N],mtag[N],atag[N],bl[N],blo; void reserve(int x) { for(int i=(x-1)*blo+1; i<=min(x*blo,n); i++) v[i]=(v[i]*mtag[x]%mod+atag[x]%mod)%mod; atag[x]=0,mtag[x]=1; } void add(int f,int l,int r,int c) { reserve(bl[l]); for(int i=l; i<=min(bl[l]*blo,r); i++) { if(f) v[i]=v[i]*c%mod; else v[i]=(v[i]+c)%mod; } if(bl[l]!=bl[r]) { reserve(bl[r]); for(int i=(bl[r]-1)*blo+1; i<=r; i++) if(f) v[i]=v[i]*c%mod; else v[i]=(v[i]+c)%mod; } for(int i=bl[l]+1; i<=bl[r]-1; i++) { if(f) atag[i]=atag[i]*c%mod,mtag[i]=mtag[i]*c%mod; else atag[i]=(atag[i]+c)%mod; } } int main() { scanf("%d",&n); blo=sqrt(n); for(int i=1; i<=n; i++) { scanf("%d",&v[i]); bl[i]=(i-1)/blo+1; mtag[bl[i]]=1; } for(int opt,l,r,c,i=1; i<=n; i++) { scanf("%d%d%d%d",&opt,&l,&r,&c); if(opt==2) printf("%d ",(v[r]*mtag[bl[r]]%mod+atag[bl[r]]%mod)%mod); else add(opt,l,r,c); } return 0; }
P3373 【模板】线段树 2
自己的分块自带大常数,qwq,只能做$70$分,还是太弱啦。。。
// luogu-judger-enable-o2 # pragma GCC optimize "O3" #include<bits/stdc++.h> #define N 200005 #define LL long long #define IL inline #define RG register using namespace std; IL void in(RG LL &x){ register char c=getchar();x=0;int f=1; while(!isdigit(c)){if(c=='-') f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} x*=f; } IL void print(RG LL x){ if(x>9) print(x/10); putchar(x%10+'0'); } LL n,v[N],mtag[N],atag[N],bl[N],blo,mod,m,sum[N]; IL void reserve(RG LL x) { for(LL i=(x-1)*blo+1; i<=min(x*blo,n); i++) v[i]=(v[i]*mtag[x]%mod+atag[x]%mod)%mod; atag[x]=0,mtag[x]=1; } IL void add(RG LL f,RG LL l,RG LL r,RG LL c) { reserve(bl[l]); for(RG LL i=l; i<=min(bl[l]*blo,r); i++) { if(f==1) sum[bl[l]]+=v[i]*(c-1)%mod,v[i]=v[i]*c%mod; else v[i]=(v[i]+c)%mod,sum[bl[l]]+=c; sum[bl[l]]%=mod; } if(bl[l]!=bl[r]) { reserve(bl[r]); for(RG LL i=(bl[r]-1)*blo+1; i<=r; i++) { if(f==1) sum[bl[r]]+=v[i]*(c-1)%mod,v[i]=v[i]*c%mod; else v[i]=(v[i]+c)%mod,sum[bl[r]]+=c; sum[bl[r]]%=mod; } for(RG LL i=bl[l]+1; i<=bl[r]-1; i++) { if(f==1) atag[i]=atag[i]*c%mod,mtag[i]=mtag[i]*c%mod,sum[i]=c*sum[i]%mod; else atag[i]=(atag[i]+c)%mod,sum[i]=(sum[i]+blo*c)%mod; } } } IL LL query(RG LL l,RG LL r) { RG LL ans=0; for(RG LL i=l; i<=min(bl[l]*blo,r); i++) ans=(ans+v[i]*mtag[bl[l]]%mod+atag[bl[l]]%mod)%mod; if(bl[l]!=bl[r]) { for(RG LL i=(bl[r]-1)*blo+1; i<=r; i++) ans=(ans+v[i]*mtag[bl[r]]%mod+atag[bl[r]]%mod)%mod; } for(RG LL i=bl[l]+1; i<=bl[r]-1; i++) ans=(ans+sum[i])%mod; return ans; } int main() { in(n),in(m),in(mod); blo=sqrt(n); for(RG LL i=1; i<=n; i++) { in(v[i]); bl[i]=(i-1)/blo+1; mtag[i]=1; sum[bl[i]]=(sum[bl[i]]+v[i])%mod; } for(RG LL opt,l,r,c,i=1; i<=m; i++) { in(opt),in(l),in(r); if(opt==3) print(query(l,r)%mod),putchar(' '); else in(c),add(opt,l,r,c); } return 0; }