根号分块
根号分块
分块是一种借助了线段树的序列区间化+懒标记的思想。
- 区间序列化:将序列分为若干个等长的块,每一块有一段管理区间,用于存储该区间的某些信息,如区间之和、区间之积、可重复贡献性问题(区间最值、区间GCD)等。
设序列长度为 n n n,块长为 b b b,则可分为 n b \frac{n}{b} bn块。最后一块可能是并非完整的碎片块。 - 分块
i
i
i的管理区间
[
L
,
R
]
[L,R]
[L,R]:
L
=
(
i
−
1
)
×
b
+
1
L=(i-1)\times b+1
L=(i−1)×b+1(上个分块
R
+
1
R+1
R+1);
R
=
{
i
×
b
(整块)
n
(碎块)
R=
R={i×bn(整块)(碎块) - 对于元素 i i i,设其位于第 k k k个分块上:由分块管理区间可知 ( k − 1 ) × b + 1 ≤ i ≤ k × b (k-1)\times b+1\le i\le k\times b (k−1)×b+1≤i≤k×b,移项得 k − 1 ≤ i − 1 b , i − 1 b < i b ≤ k ⇒ k − 1 ≤ i − 1 b < k ⇒ k − 1 = ⌊ i − 1 b ⌋ k-1\le \frac{i-1}{b},\frac{i-1}{b}<\frac{i}{b}\le k\Rightarrow k-1\le \frac{i-1}{b}<k\Rightarrow k-1=\lfloor\frac{i-1}{b}\rfloor k−1≤bi−1,bi−1<bi≤k⇒k−1≤bi−1<k⇒k−1=⌊bi−1⌋,故 k = ⌊ i − 1 b ⌋ + 1 k=\lfloor\frac{i-1}{b}\rfloor+1 k=⌊bi−1⌋+1
- 懒标记:当涉及区间的统一操作时,不逐一修改每个元素,而是在区间所属的块上打上懒标记,进行统一的修改
如何分块?设操作区间为 [ L , R ] [L,R] [L,R]:
- 若 [ L , R ] [L,R] [L,R]在一个整块内,称为只与一个区块有交,直接遍历 [ L , R ] [L,R] [L,R]操作即可,复杂度最多为 O ( b ) O(b) O(b);
- 若 [ L , R ] [L,R] [L,R]不在一个整块内,称为与多个块有交,则操作由三部分拼接:以 L L L起始的后缀碎块,中间整块,以 R R R结尾的前缀碎块。处理方法为整块打包维护,碎块逐个枚举:对于碎块,由于存在元素 ∉ [ L , R ] \notin[L,R] ∈/[L,R],因此需要逐个遍历碎块中 ∈ [ L , R ] \in[L,R] ∈[L,R]的元素进行操作;对于整块,则直接调用其管理区间的信息+懒标记进行整体操作。最坏情况下 [ L , R ] [L,R] [L,R]近似为序列长度,复杂度 O ( n b + b ) O(\frac{n}{b}+b) O(bn+b)。
- 分块长度应使 n b + b \frac{n}{b}+b bn+b最小化。根据基本不等式, n b + b ≥ 2 n \frac{n}{b}+b\ge2\sqrt{n} bn+b≥2n。因此分块长度一般为 n \sqrt n n为最优解,因此可在 O ( q n ) O(q\sqrt{n}) O(qn)的复杂度完成区间修改+区间查询问题。以 n \sqrt{n} n长度进行分块的方法称为根号分块法,是最简单的分块方法。
//下标均从1开始
int n,len=sqrt(n),num=ceil(n/(float)len);//若有碎块需+1
struct node{
int data,pos;//数据,所在区块
}a[n+1];
struct block{
int l,r,data,tag;//块的管理区间[l,r],区间信息,懒标记
}b[num+1];
void init(){
for(int i=1;i<=num;i++) b[i].l=(i-1)*len+1,b[i].r=i*len;
b[num].r=n;//重新计算碎块管理区间
for(int i=1;i<=n;i++) a[i].pos=(i-1)/len+1;//获取第i个下标所属的块
for(int i=1;i<=num;i++)//遍历所有分块
for(int j=b[i].l;j<=b[i].r;j++)//遍历块内所有元素
b[i].data=opt(b[i].data,a[j]);
}
由于只遍历了原序列一次,因此预处理复杂度为 O ( n ) O(n) O(n)
区间修改
void modify(int l,int r,int d){//以[l,r]均+d为例
int pl=a[l].pos,pr=a[r].pos;
if(pl==pr){//l,r在相同块中
for(int i=l;i<=r;i++) a[i].data+=d;
b[pl].data+=(r-l+1)*d;
}else{
//处理l所在碎片
for(int i=l;i<=b[pl].r;i++) a[i].data+=d;
b[pl].data+=(b[pl].r-l+1)*d;
//处理中间的整块
for(int i=pl+1;i<pr;i++) b[i].tag+=d;
//处理r所在碎片
for(int i=b[pr].l;i<=r;i++) a[i].data+=d;
b[pr].data+=(r-b[pr].l+1)*d;
}
}
区间查询
int query(int l,int r){//以查询[l,r]之和为例
int pl=a[l].pos,pr=a[r].pos,ans=0;
if(pl==pr){
for(int i=l;i<=r;i++) ans+=a[i].data;
ans+=b[pl].tag*(r-l+1);//加上懒标记
}else{
//处理l所在碎片
for(int i=l;i<=b[pl].r;i++) ans+=a[i].data;
ans+=b[pl].tag*(b[pl].r-l+1);
//处理中间的整块
for(int i=pl+1;i<pr;i++) ans+=b[i].data+b[i].tag*(b[i].r-b[i].l+1);
//处理r所在碎片
for(int i=b[pr].l;i<=r;i++) ans+=a[i].data;
ans+=b[pr].tag*(r-b[pr].l+1);
}
return ans;
}
扩展:分块求解静态RMQ问题的其他方法
由于问题为静态RMQ,则只有查询没有修改,一旦确定区块最值后,其将不再改变。
设序列长度为 n n n,分块长度为 b b b,序列被分为 n b \frac{n}{b} bn块,有 q q q次询问。若操作区间 [ L , R ] [L,R] [L,R]与多个块有交,则被分为以 L L L起始的后缀碎块、中间若干个整块、以 R R R结尾的前缀碎块3个部分。对于中间若干个区块的整体最值,其由若干个区块最值拼接而成。
预处理
- 块间预处理:双指针依次遍历所有区块,预处理出区块 i i i到区块 j ( i , j ∈ [ 1 , n b ] , i < j ) j(i,j\in[1,\frac{n}{b}],i<j) j(i,j∈[1,bn],i<j)的最值。复杂度 O ( ( n b ) 2 ) O((\frac{n}{b})^2) O((bn)2)。此预处理是为能处理区块拼接成中间整块的最值查询。
- 块内预处理:依次枚举每个块,分别计算出块内每个元素的前缀和后缀元素最值。复杂度 O ( n ) O(n) O(n)。此预处理是为了能处理前后缀碎块的最值查询。
预处理的复杂度 O ( ( n b ) 2 ) O((\frac{n}{b})^2) O((bn)2)。
查询
- 若询问 [ L , R ] [L,R] [L,R]与多个区块有交,复杂度 O ( 1 ) O(1) O(1);否则复杂度 O ( b ) O(b) O(b)。
查询的复杂度 O ( q × b ) O(q\times b) O(q×b)。
分块长度分析
以上步骤整体复杂度 O ( ( n b ) 2 + q × b ) O((\frac{n}{b})^2+q\times b) O((bn)2+q×b)。
根据基本不等式可知 ( n b ) 2 + q × b ≥ 2 n 2 × q b (\frac{n}{b})^2+q\times b\ge2\sqrt{\frac{n^2\times q}{b}} (bn)2+q×b≥2bn2×q,当 ( n b ) 2 = q × b (\frac{n}{b})^2=q\times b (bn)2=q×b 时复杂度取最小值。
即 b = n 2 3 q 3 b=\frac{\sqrt[3]{n^2}}{\sqrt[3]{q}} b=3q3n2,复杂度 O ( ( n × q ) 2 3 ) O(\sqrt[3]{(n\times q)^2}) O(3(n×q)2)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具