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