学习笔记:分块
前言
非常粗略
概念
什么是分块算法?
很简单 就是暴力 把一段长度为 \(n\) 的序列 分成 \(\sqrt{n}\) 块 块长为 \(\sqrt n\) 然后进行一系列暴力乱搞
它的好处就是非常暴力 好!
先来看一道 板子
题目要求我们区间加一个数 区间查询一段和
这个东西怎么搞?
考虑分块!
首先呢 把原数列分为了 \(\sqrt n\) 块 可能末尾还会多一块不足 \(\sqrt n\) 长度的块
然后 我们需要预处理以下的值:
- \(bel_x\) 查询每个数在哪个块
- \(L_x\) 这个块的左端点
- \(R_x\) 这个块的右端点
- \(block\) 块长
- \(tot\) 块数
注意处理最后一块的边界和块数即可
易得每一块的左端点为 \((i-1)\times block +1\) 右端点为 \(i\times block\)
根据这样算一下即可
void build()
{
block=sqrt(n);
tot=n/block;
if(n%block) tot++;
for(int i=1;i<=tot;i++)
L[i]=R[i-1]+1,R[i]=i*block;
R[tot]=n;
for(int i=1;i<=n;i++)
bel[i]=(i-1)/block+1,sum[bel[i]]+=a[i];
}
预处理完毕 思考怎么修改/求中间一段
其实很简单
- 对于中间的整段 借用线段树的思想 打个 \(tag\) 走路即可 不超过 \(O(\sqrt n)\)
- 对于左右的零散段 暴力改即可 不超过 \(O(\sqrt n)\)
时间复杂度 \(O(\sqrt n)\)
注意特判在一个块内的即可
void updata(int l,int r,ll x)
{
int ls=bel[l],rs=bel[r];
if(ls==rs)
{
for(int i=l;i<=r;i++)
a[i]+=x,sum[ls]+=x;
return ;
}
for(int i=l;i<=R[ls];i++)
a[i]+=x,sum[ls]+=x;
for(int i=L[rs];i<=r;i++)
a[i]+=x,sum[rs]+=x;
for(int i=ls+1;i<=rs-1;i++)
tag[i]+=x,sum[i]+=(R[i]-L[i]+1)*x;
return ;
}
然后是查询
借用和修改一样的操作就行 时间复杂度 \(O(\sqrt n)\)
ll query(int l,int r)
{
int ls=bel[l],rs=bel[r];
ll ans=0;
if(ls==rs)
{
for(int i=l;i<=r;i++)
ans+=a[i]+tag[ls];
return ans;
}
for(int i=l;i<=R[ls];i++)
ans+=a[i]+tag[ls];
for(int i=L[rs];i<=r;i++)
ans+=a[i]+tag[rs];
for(int i=ls+1;i<=rs-1;i++)
ans+=sum[i];
return ans;
}
这样 我们优雅的暴力程序就出来了 时间复杂度 \(O(n\sqrt n)\) 还是很不错的