学习笔记——分块
分块
我们先引入一道题:
有一个长度为 \(N\) 的数列 \(A\),然后输入 \(Q\) 行指令操作。一种是将数列中 \(l \sim r\) 个数都加 \(d\),另一种则是询问数列中 \(l \sim r\) 个数的和。
用线段树或树状数组能在 \(\mathcal{O}((N + Q)\log N)\) 的时间内解决,那么用分块呢?
预处理
把数列 \(A\) 分成若干长度不超过 \(\lfloor\sqrt{N}\rfloor\) 的段,第 \(i\) 段的左端点是 \((i - 1)\lfloor\sqrt{N}\rfloor +1\),右端点是 \(\min(i\lfloor\sqrt{N}\rfloor,N)\)。
然后算出每段的前缀和数组 \(sum\) ,将每段的增量标记 \(add\) 赋值为 \(0\)。
tmp=sqrt(n);
for(int i=1;i<=tmp;i++){
L[i]=(i-1)*sqrt(n)+1;
R[i]=i*sqrt(n);
}
if(R[tmp]<n)tmp++,L[tmp]=R[tmp-1]+1,R[tmp]=n;
for(int i=1;i<=tmp;i++){
for(int j=L[i];j<=R[i];j++){
pos[j]=i;
sum[i]+=a[j];
}
}
区间加法
- 如果 \(l\) 与 \(r\) 同时处于第 \(i\) 段内,则直接将 \(A\left[l\right],A\left[l+1\right],\cdots,A\left[r\right]\) 都加 \(d\) ,同时改变前缀和数组 \(sum\left[i\right] += d * (r-l+1)\)。
- 设 \(l\) 在 \(p\) 段,\(r\) 在 \(q\) 段。那么对于 \(i \in \left[p + 1,q - 1\right]\),令 \(add\left[i\right]+=d\)。而对于不足一整段的开头和结尾两部分则按照第 \(1\) 种情况更新。
void change(int l,int r,int d){
int p=pos[l],q=pos[r];//pos数组存储每个位置是哪一段
if(p==q){
for(int i=l;i<=r;i++)a[i]+=d;
sum[p]+=d*(r-l+1);
}else{
for(int i=p+1;i<=q-1;i++)add[i]+=d;
for(int i=l;i<=R[p];i++)a[i]+=d;
sum[p]+=d*(R[p]-l+1);
for(int i=L[q];i<=r;i++)a[i]+=d;
sum[q]+=d*(r-L[q]+1);
}
}
区间求和
- 如果 \(l\) 与 \(r\) 同时处于第 \(i\) 段内,则答案就是 \(add\left[i\right]*(r - l + 1)+(A\left[l\right]+A\left[l+1\right]+\cdots+A\left[r\right])\)。
- 设 \(l\) 在 \(p\) 段,\(r\) 在 \(q\) 段,\(ans\) 为 \(0\)。那么对于 \(i \in \left[p + 1,q - 1\right]\),令 \(ans+=sum\left[i\right]+add\left[i\right]*len\left[i\right]\),\(len\left[i\right]\) 表示第 \(i\) 段长度。而对于不足一整段的开头和结尾两部分则按照第 \(1\) 种情况计算。
int ask(int l,int r){
int p=pos[l],q=pos[r];
int ans=0;
if(p==q){
for(int i=l;i<=r;i++)ans+=a[i];
ans+=add[p]*(r-l+1);
}else{
for(int i=p+1;i<=q-1;i++)
ans+=sum[i]+add[i]*(R[i]-L[i]+1);
for(int i=l;i<=R[p];i++)ans+=a[i];
ans+=add[p]*(R[p]-l+1);
for(int i=L[q];i<=r;i++)ans+=a[i];
ans+=add[q]*(r-L[q]+1);
}
return ans;
}
Example
这里以Luogu P2801为例。
分块好题,只不过预处理时需要排序。
#include<bits/stdc++.h>
#define int long long
#define MAX 1000005
using namespace std;
int n,q,a[MAX],b[MAX],tmp,L[MAX],R[MAX],add[MAX],sum[MAX],pos[MAX];
char ch;
void change(int l,int r,int w){
int p=pos[l],q=pos[r];
if(p==q){
for(int i=l;i<=r;i++)a[i]+=w;
for(int i=L[p];i<=R[q];i++)b[i]=a[i];
sort(b+L[p],b+R[q]+1);
}else{
for(int i=p+1;i<=q-1;i++)add[i]+=w;
for(int i=l;i<=R[p];i++)a[i]+=w;
for(int i=L[p];i<=R[p];i++)b[i]=a[i];
sort(b+L[p],b+R[p]+1);
for(int i=L[q];i<=r;i++)a[i]+=w;
for(int i=L[q];i<=R[q];i++)b[i]=a[i];
sort(b+L[q],b+R[q]+1);
}
}
int ask(int l,int r,int w){
int p=pos[l],q=pos[r];
int ans=0,t;
if(p==q){
for(int i=l;i<=r;i++){
if(add[p]+a[i]>=w)ans++;
}
}else{
for(int i=p+1;i<=q-1;i++){
t=lower_bound(b+L[i],b+R[i]+1,w-add[i])-b;
ans+=R[i]-t+1;
}
for(int i=l;i<=R[p];i++){
if(add[p]+a[i]>=w)ans++;
}
for(int i=L[q];i<=r;i++){
if(add[q]+a[i]>=w)ans++;
}
}
return ans;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++)cin>>a[i],b[i]=a[i];
tmp=sqrt(n);
for(int i=1;i<=tmp;i++){
L[i]=(i-1)*sqrt(n)+1;
R[i]=i*sqrt(n);
}
if(R[tmp]<n)tmp++,L[tmp]=R[tmp-1]+1,R[tmp]=n;
for(int i=1;i<=tmp;i++){
for(int j=L[i];j<=R[i];j++){
pos[j]=i;
sum[i]+=a[j];
}
}
for(int i=1;i<=tmp;i++){
sort(b+L[i],b+R[i]+1);
}
int l,r,w;
while(q--){
cin>>ch>>l>>r>>w;
if(ch=='M')change(l,r,w);
else cout<<ask(l,r,w)<<'\n';
}
return 0;
}