分块
分块是将线段树的懒标记方法一般化,可证明通常情况下以 n \sqrt n n分块是最优解。
分块思想核心: 整块打包维护 碎块逐个枚举
int len,num;//len:每块长度,num:分块数量
int begin[],end[],pos[],sum[],add[];//begin,end:每块的始末下标 pos:每个下标所属块 sum:每块区间和 add:整块增量标记
void init(){len=sqrt(n);num=n/len;if(n%len) num++;//处理尾部有碎块情形for(int i=1;i<=num;i++){//获取第i块的首尾下标begin[i]=(i-1)*len+1;end[i]=i*len;}end[num]=n;//更新最后一碎块尾下标for(int i=1;i<=n;i++) pos[i]=(i-1)/len+1;//获取第i个下标所属的块for(int i=1;i<=num;i++)for(int j=begin[i];j<=end[i];j++)sum[i]+=a[j];//获取每块区间和
}
区间修改
以在 [ l , r ] [l,r] [l,r]元素加d为例
extern int l,r,d,a[];
void update(){int beg=pos[l],ed=pos[r];//获取l和r所属的块if(beg==ed){//l,r在相同块中for(int i=l;i<=r;i++) a[i]+=d;sum[p]+=(r-l+1)*d;//更新该块区间之和}else{//l,r中间跨越了整块for(int i=l;i<=end[beg];i++) a[i]+=d;//将l所属的块处理完sum[beg]+=(end[beg]-l+1)*d;for(int i=beg+1;i<ed;i++) add[i]+=d;//处理中间的整块for(int i=begin[ed];i<=r;i++) a[i]+=d;sum[ed]+=(r-begin[ed]+1)*d;}
}
总结:涉及到更新碎片,只在原数组a和块区间和数组sum上更新,不更新add整块增量标记
区间查询
extern int l,r,a[];
int query(){int beg=pos[l],ed=pos[r];int ans=0;if(beg==ed){//l,r在相同块中for(int i=l;i<=r;i++) ans+=a[i];ans+=add[p]*(r-l+1);//此块可能被整体修改过}else{//l,r不在相同块中for(int i=l;i<=end[l];i++) ans+=a[i];ans+=add[beg]*(end[l]-l+1);for(int i=beg+1;i<ed;i++) ans+=sum[i]+add[i]*(end[i]-begin[i]+1);//不能写成add[i]*len,因为无法确定r是否在最后一个碎块上for(int i=begin[ed];i<=r;i++) ans+=a[i];ans+=add[ed]*(r-begin[ed]+1);}return ans;
}