数列分块!!!
世界上怎么会有分块那么可爱的思想!!!
一、概念
分块,你可以说它是数据结构,也可以说是一种思想。
而数列分块呢,就是将一段序列分成许多块,分别维护每块的信息,来求出一段区间的信息(比如最大值,区间和等等)。
可以用下面的图片来理解:
Emm,搞错了:
对于区间询问 + 修改的问题,我们就可以使用分块的思想,在区间覆盖的整块部分直接修改整块的信息,而不完整块直接暴力处理(太可爱了,真的很好写!)
设块长为 \(b\),该算法时间复杂度一般为:\(O(B + \frac{N}{B})\),当 \(B = \sqrt{N}\) 时取得最小。
二、写法
1.处理每块左右端点
for(int i = 1;i <= n;i ++){
bel[i] = (i - 1) / len + 1;//bel[i]表示属于那个块,len为块长
}
for(int i = 1;i <= num;i ++){
l[i] = (i - 1) * s + 1;
r[i] = i * s;
}//l[i] 为一个区间的左端点,r[i]为一个区间的右端点,num为块的数量
r[num] = n;
// 注意,最后一块可能不完整!
2.修改操作
- 区间修改
和线段树类似,分块也可以有 lazy_tag,对于直接修改整块,可以直接打 Tag,当查询 or 修改到该块的部分时再下放标记。
区间修改一般是这么一个框架:
if(bel[l] == bel[r]){
pushdown(bel[l]);
暴力执行
}else{
pushdown(bel[l]);
for(int i = l;i <= R[bel[l];i ++){
暴力
}
pushdown(bel[r]);
for(int i = L[bel[r]] ;i <= r;i ++){
暴力
}
for(int i = bel[l] + 1;i <= bel[r] - 1;i ++){
块信息整体修改
}
}
- 单点修改
直接暴力修改单点,再对所属块,重新记录信息。
Link:
a[x] = ···;
for(int i = L[bel[x]] ;i <= R[bel[x]];i ++){
val[bel[x]] = ···;
}
3.查询操作
- 区间查询
区间查询与区间修改差不了太多:
if(bel[l] == bel[r]){
pushdown (bel[l]);
暴力统计
}else{
pushdown (bel[l]);
for(int i = l;i <= R[bel[l];i ++){
暴力统计
}
pushdown (bel[r]);
for(int i = L[bel[r]] ;i <= r;i ++){
暴力统计
}
for(int i = bel[l] + 1;i <= bel[r] - 1;i ++){
块信息整体统计
}
}
- 单点查询
先将所属块标记下放,直接获取 \(Ans\)
Link:
pushdown(bel[x]);
ans = a[x];
二、常见错误
-
在最后一块的右端点没有设为 \(n\)
-
在查询时忘记下放标记
-
标记改变错误
三、技巧 / 套路
- 对于种类数的统计,由于块的数量只有 \(\sqrt{N}\),于是乎可以直接开下 \(cnt[x][val]\) 的数组,直接统计。 link: P3313 [SDOI2014] 旅行