【分块入门】分块
summary
前面几个都是用来理解分块的思想和练习 能用线段树还是用线段树叭 像lch说的先考虑各种数据结构的优势 尽量用最简单的最适合的
分块一 区间加法 单点查询
给出一个长为n的数列,以及n个操作,操作涉及区间加法,单点查值
给每个块设置一个加法标记,每次操作对每个整块直接O(1)标记,而不完整的块由于元素比较少,暴力修改元素的值。
每次询问时返回元素的值加上其所在块的加法标记
这样每次操作的复杂度是O(n/m)+O(m),根据均值不等式,当m取√n时总复杂度最低
可以画一画区间更直观QAQ
#include<bits/stdc++.h> using namespace std; #define Max(x,y) (x)>(y)?(x):(y) #define Min(x,y) (x)<(y)?(x):(y) const int N=5e4+5inf=0x3f3f3f3f; int n,base,a[N],bl[N],addtg[N]; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } void add(int l,int r,int c){ for(int i=l;i<=(Min(r,bl[l]*base));++i) a[i]+=c; if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*base+1;i<=r;++i) a[i]+=c; for(int i=bl[l]+1;i<=bl[r]-1;++i) addtg[i]+=c; } int main(){ // freopen("in.txt","r",stdin); rd(n),base=sqrt(n); for(int i=1;i<=n;++i) rd(a[i]); for(int i=1;i<=n;++i) bl[i]=(i-1)/base+1; for(int i=1,op,l,r,c;i<=n;++i){ rd(op),rd(l),rd(r),rd(c); if(!op) add(l,r,c); else printf("%d\n",a[r]+addtg[bl[r]]); } return 0; }
分块二 区间加法 询问区间内小于某数的元素个数
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的元素个数。
对于每次区间操作:
1.不完整的块 的O(√n)个元素怎么处理?
2.O(√n)个 整块 怎么处理?
3.要预处理什么信息(复杂度不能超过后面的操作)?
先来思考只有询问操作的情况,不完整的块枚举统计即可;而要在每个整块内寻找小于一个值的元素数,于是我们不得不要求块内元素是有序的,这样就能使用二分法对块内查询,需要预处理时每块做一遍排序,复杂度O(nlogn),每次查询在√n个块内二分,以及暴力2√n个元素,总复杂度O(nlogn + n√nlog√n)。
#include<bits/stdc++.h> using namespace std; #define Max(x,y) (x)>(y)?(x):(y) #define Min(x,y) (x)<(y)?(x):(y) const int N=5e4+5,M=500+5,inf=0x3f3f3f3f; int n,base,a[N],bl[N],addtg[N]; vector<int>vec[M]; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } void reset(int x){ vec[x].clear(); for(int i=(x-1)*base+1;i<=(Min(x*base,n));++i) vec[x].push_back(a[i]); sort(vec[x].begin(),vec[x].end()); } void add(int l,int r,int k){ for(int i=l;i<=(Min(r,bl[l]*base));++i) a[i]+=k; reset(bl[l]); if(bl[l]!=bl[r]){ for(int i=(bl[r]-1)*base+1;i<=r;++i) a[i]+=k; reset(bl[r]); } for(int i=bl[l]+1;i<=bl[r]-1;++i) addtg[i]+=k; } int query(int l,int r,int k){ int ans=0; for(int i=l;i<=(Min(r,bl[l]*base));++i) if(a[i]+addtg[bl[i]]<k) ++ans; if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*base+1;i<=r;++i) if(a[i]+addtg[bl[i]]<k) ++ans; for(int i=bl[l]+1,x;i<=bl[r]-1;++i){ x=k-addtg[i]; ans+=lower_bound(vec[i].begin(),vec[i].end(),x)-vec[i].begin(); } return ans; } int main(){ freopen("in.txt","r",stdin); rd(n),base=sqrt(n); for(int i=1;i<=n;++i) rd(a[i]),bl[i]=(i-1)/base+1,vec[bl[i]].push_back(a[i]); for(int i=1;i<=bl[n];++i) sort(vec[i].begin(),vec[i].end()); for(int i=1,op,l,r,k;i<=n;++i){ rd(op),rd(l),rd(r),rd(k); if(!op) add(l,r,k); else printf("%d\n",query(l,r,k*k)); } return 0; }
分块三 区间加法 询问前驱
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(比其小的最大元素
接着第二题的解法,其实只要把块内查询的二分稍作修改即可。
不过这题其实想表达:可以在块内维护其它结构使其更具有拓展性,比如放一个 set ,这样如果还有插入、删除元素的操作,会更加的方便。
分块的调试检测技巧:
可以生成一些大数据,然后用两份分块大小不同的代码来对拍,还可以根据运行时间尝试调整分块大小,减小常数。
本题base=√n比1000慢很多
#include<bits/stdc++.h> using namespace std; #define Max(x,y) (x)>(y)?(x):(y) #define Min(x,y) (x)<(y)?(x):(y) const int N=1e5+5,M=1000+5,inf=0x3f3f3f3f; int n,base,a[N],bl[N],addtg[N]; set<int>s[M]; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } void add(int l,int r,int k){ for(int i=l;i<=(Min(r,bl[l]*base));++i){ s[bl[i]].erase(a[i]); a[i]+=k; s[bl[i]].insert(a[i]); } if(bl[l]!=bl[r]){ for(int i=(bl[r]-1)*base+1;i<=r;++i){ s[bl[i]].erase(a[i]); a[i]+=k; s[bl[i]].insert(a[i]); } } for(int i=bl[l]+1;i<=bl[r]-1;++i) addtg[i]+=k; } int query(int l,int r,int k){ int ans=-1; for(int i=l,x;i<=(Min(r,bl[l]*base));++i){ x=a[i]+addtg[bl[i]]; if(x<k) ans=Max(ans,x); } if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*base+1,x;i<=r;++i){ x=a[i]+addtg[bl[i]]; if(x<k) ans=Max(ans,x); } for(int i=bl[l]+1,x;i<=bl[r]-1;++i){ x=k-addtg[i]; set<int>::iterator iter=s[i].lower_bound(x); if(iter==s[i].begin()) continue; --iter; ans=Max(ans,*iter+addtg[i]); } return ans; } int main(){ freopen("in.txt","r",stdin); rd(n),base=1000; for(int i=1;i<=n;++i) rd(a[i]),bl[i]=(i-1)/base+1,s[bl[i]].insert(a[i]); for(int i=1,op,l,r,k;i<=n;++i){ rd(op),rd(l),rd(r),rd(k); if(!op) add(l,r,k); else printf("%d\n",query(l,r,k)); } return 0; }
分块四 区间加&区间求和
给出一个长为n的数列,以及n个操作,操作涉及区间加法,区间求和
而要想快速统计完整块的答案,需要维护每个块的元素和,先要预处理一下。
考虑区间修改操作,不完整的块直接改,顺便更新块的元素和;完整的块类似之前标记的做法,直接根据块的元素和所加的值计算元素和的增量。
#include<bits/stdc++.h> using namespace std; #define Max(x,y) (x)>(y)?(x):(y) #define Min(x,y) (x)<(y)?(x):(y) #define ll long long const int N=5e4+5,M=500+5,inf=0x3f3f3f3f,P=9999973; int n,base,bl[N]; ll a[N],sum[N],addtg[N]; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } void add(int l,int r,ll k){ for(int i=l;i<=(Min(r,bl[l]*base));++i) a[i]+=k,sum[bl[i]]+=k; if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*base+1;i<=r;++i) a[i]+=k,sum[bl[i]]+=k; for(int i=bl[l]+1;i<=bl[r]-1;++i) addtg[i]+=k; } ll query(int l,int r,ll k){ ll ans=0; for(int i=l;i<=(Min(r,bl[l]*base));++i) ans=(ans+a[i]+addtg[bl[i]])%k; if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*base+1;i<=r;++i) ans=(ans+a[i]+addtg[bl[i]])%k; for(int i=bl[l]+1;i<=bl[r]-1;++i) ans=(ans+sum[i]+addtg[i]*base)%k; return ans; } int main(){ freopen("in.txt","r",stdin); rd(n),base=sqrt(n); for(int i=1;i<=n;++i) rd(a[i]),bl[i]=(i-1)/base+1,sum[bl[i]]+=a[i]; for(int i=1,op,l,r,k;i<=n;++i){ rd(op),rd(l),rd(r),rd(k); if(!op) add(l,r,k); else printf("%lld\n",query(l,r,k+1)%(k+1)); } return 0; }
分块五 区间开方&区间求和
和线段树做这题的思想差不多 也要维护一个当前区间是否全部为0/1
#include<bits/stdc++.h> using namespace std; #define Max(x,y) (x)>(y)?(x):(y) #define Min(x,y) (x)<(y)?(x):(y) #define ll long long const int N=5e4+5,M=500+5,inf=0x3f3f3f3f,P=9999973; int n,base,bl[N]; ll a[N],sum[N],addtg[N],tg[N]; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } void updat(int x){ if(tg[x]) return; tg[x]=1,sum[x]=0; for(int i=(x-1)*base+1;i<=x*base;++i){ a[i]=sqrt(a[i]),sum[x]+=a[i]; if(a[i]>1) tg[x]=0; } } void sqr(int l,int r){ for(int i=l;i<=(Min(r,bl[l]*base));++i){ sum[bl[i]]-=a[i]; a[i]=sqrt(a[i]); sum[bl[i]]+=a[i]; } if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*base+1;i<=r;++i){ sum[bl[i]]-=a[i]; a[i]=sqrt(a[i]); sum[bl[i]]+=a[i]; } for(int i=bl[l]+1;i<=bl[r]-1;++i) updat(i); } ll query(int l,int r){ ll ans=0; for(int i=l;i<=(Min(r,bl[l]*base));++i) ans+=a[i]; if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*base+1;i<=r;++i) ans+=a[i]; for(int i=bl[l]+1;i<=bl[r]-1;++i) ans+=sum[i]; return ans; } int main(){ freopen("in.txt","r",stdin); rd(n),base=sqrt(n); for(int i=1;i<=n;++i) rd(a[i]),bl[i]=(i-1)/base+1,sum[bl[i]]+=a[i]; for(int i=1,op,l,r,k;i<=n;++i){ rd(op),rd(l),rd(r),rd(k); if(!op) sqr(l,r); else printf("%lld\n",query(l,r)); } return 0; }
分块六 单点插入&单点查询
给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。
此题每块内可以放一个动态的数组,每次插入时先找到位置所在的块,再暴力插入,把块内的其它元素直接向后移动一位,当然用链表也是可以的。
如果先在一个块有大量单点插入,这个块的大小会大大超过√n,那块内的暴力就没有复杂度保证了 还需要引入一个操作:重新分块(重构)
每根号n次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为√n,所以重构的复杂度没有问题,而且保证了每个块的大小相对均衡。当然,也可以当某个块过大时重构,或者只把这个块分成两半。
并未完全理解代码 所以先折叠
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; #define Max(x,y) (x)>(y)?(x):(y) #define Min(x,y) (x)<(y)?(x):(y) #define ll long long #define Abs(x) (x)<0?-x:x; #define rg register const int N=1e5+5,M=1000+5,inf=0x3f3f3f3f,P=9999973; int n,m,base,a[N],st[N],top; vector<int>v[M]; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } pair<int,int>query(int pos){ int x=1; while(pos>v[x].size()) pos-=v[x].size(),++x; return make_pair(x,pos-1); } void rebuild(){ top=0; for(int i=1;i<=m;++i){ for(vector<int>::iterator j=v[i].begin();j!=v[i].end();++j) st[++top]=*j; v[i].clear(); } int base2=sqrt(top); for(int i=1;i<=top;++i) v[(i-1)/base2+1].push_back(st[i]); m=(top-1)/base2+1; } void insert(int pos,int x){ pair<int,int> t=query(pos); v[t.first].insert(v[t.first].begin()+t.second,x); if(v[t.first].size()>base*20) rebuild(); } int main(){ freopen("in.txt","r",stdin); rd(n),base=sqrt(n),m=(n-1)/base+1; for(int i=1;i<=n;++i) rd(a[i]),v[(i-1)/base+1].push_back(a[i]); for(int i=1,op,l,r,k;i<=n;++i){ rd(op),rd(l),rd(r),rd(k); if(!op) insert(l,r); else{ pair<int,int> t=query(r); printf("%d\n",v[t.first][t.second]); } } return 0; }