块状数组
分块是一种 思想 ,简洁的来说,就是把一个整体划分为若干个小块,对整块 整体 处理,零散块 单独(暴力) 处理。
块状数组
但花花这里所要介绍的是一种基于分块思想的数据结构 —— 块状数组
首先对于提高以及低难度省选的区间问题,可以用线段树、树状数组等数据结构及其拓展以更优的复杂度进行解决,但都有些许劣势,例如线段树的空间过大,树状数组的适用范围小。
那是否有一种处于平衡点的数据结构呢?
块状数组是一种处理区间问题根号数据结构,对于大多数不卡根号的题目,是可以卡常通过的。
优点:通用性强,空间复杂度低,易实现(码量与线段树差不多)。
缺点:较基于分治思想的数据结构如线段树、平衡树等效率较低,仅能做到 \mathcal{O} (n \sqrt{n} ) 的时间复杂度。
先对一些名词做一些解释:
区间 :数列中连续一段的元素。
区间操作 : 将一个区间
块 : 把一个数列分成若干个不相交的区间,每个区间称为一个块。
整块 : 在一个区间操作中,完整包含于区间的块。
不完整的块 : 在一个区间操作中,除整块意外的散块,即区间两个端点所在的两个块。
块状数组把一个长度为
显然分的块数会极大的影响时间复杂度,根据一半不等式,可以得出当块长度为 出题人恶意针对除外 ,总复杂度大致为
花花的叙述能力较差,所以后面我们结合代码来讲解。
预处理
ll n,a[1000005];
ll block,cnt;
ll L[1105],R[1105],belong[1000005];
这里的
block = sqrt(n);
cnt = n/block;
if(n%block){
++cnt;
}
这里确认了块的大小以及数量,因为
for(int i=1;i<=n;++i){
belong[i] = (i-1)/block+1;
}
for(int i=1;i<=cnt;++i){
L[i] = (i-1)*block+1;
R[i] = i*block;
}
R[cnt] = n;
因为每个块的大小为
以上就是基本的初始化建块操作,不同的题目需要对其进行相应的变化。
现在我们来通过一道例题实操一下 P3372 【模板】线段树 1
简要题意:维护一个长度为
思路:对于加法,如果
对于查询操作,如果在同一个块中就进行暴力查询,否则对整块的和进行相加对不完整的块暴力处理。
拓展:P3373 【模板】线段树 2 与线段树
P2801 教主的魔法
简要题意:维护一个长度为
思路:与前面的区间加法类似,我们对于区间加法操作会在此基础上加上一个排序同时将数组进行复制,至于作用在查询的时候会讲。
void add(ll nowl,ll nowr,ll addk){
if(belong[nowl]==belong[nowr]){ //在同一块
for(int i=nowl;i<=nowr;++i){
a[i]+=addk;
}
for(int i=L[belong[nowl]];i<=R[belong[nowr]];++i){
b[i] = a[i];
}
sort(b+L[belong[nowl]],b+R[belong[nowl]]+1);
}
else{ //不同块
for(int i=nowl;i<=R[belong[nowl]];++i){ //左开
a[i]+=addk;
}
for(int i=L[belong[nowl]];i<=R[belong[nowl]];++i){
b[i] = a[i];
}
sort(b+L[belong[nowl]],b+R[belong[nowl]]+1);
for(int i=L[belong[nowr]];i<=nowr;++i){ //右开
a[i]+=addk;
}
for(int i=L[belong[nowr]];i<=R[belong[nowr]];++i){
b[i] = a[i];
}
sort(b+L[belong[nowr]],b+R[belong[nowr]]+1);
for(int i=belong[nowl]+1;i<=belong[nowr]-1;++i){
lazy[i]+=k;
}
}
}
首先对于整块来讲,我们所查询的内容和顺序无关,散块的话我们不能改变原序列,这时复制的数组就有用了,我们可以在那上面进行操作和查询,因为已经对整块排好序了,所以直接二分查找第一个大于等于
ll solve(ll nowl,ll nowr,ll check){
ll res = 0;
if(belong[nowl]==belong[nowr]){
for(int i=nowl;i<=nowr;++i){
if(lazy[belong[nowl]]+a[i]>=check){
++res;
}
}
return res;
}
else{
for(int i=nowl;i<=R[belong[nowl]];++i){
if(lazy[belong[nowl]]+a[i]>=check){
++res;
}
}
for(int i=L[belong[nowr]];i<=nowr;++i){
if(lazy[belong[nowr]]+a[i]>=check){
++res;
}
}
for(ll i=belong[nowl]+1;i<=belong[nowr]-1;++i){
res+=binary_search(i,check);
}
return res;
}
}
当然如果还按前面那样的建块是过不去的,所以我们在建块时要复制数组,并将其复制的数组按块排序一下。
这样这道题的时间复杂度就可以做到
P3203 [HNOI2010]弹飞绵羊
简要题意:给
思路:虽然这是一道省选紫题,但如果好好想想会发现这只是个单点修改单点查询的题目,所以建块以后只需要维护两个值:
所以对于查询,我们从起点出发,用
ll solve(ll che){
ll res = 0;
while(che<=n){
res+=step[che];
che = lazy[che];
}
return res;
}
对于修改,发现
void add(ll x,ll y){
a[x] = y,flag = belong[x];
for(int i=R[flag];i>=L[flag];--i){ //清除操作
step[i] = 0;
lazy[i] = 0;
}
for(int i=R[flag];i>=L[flag];--i){
if(a[i]+i>R[flag]){
step[i] = 1;
lazy[i] = a[i]+i;
}
else{
step[i]+=step[a[i]+i]+1;
lazy[i] = lazy[a[i]+i];
}
}
}
这样一道正解为
综上块状数组是一种好想且易编写的数据结构,对于区间问题,且序列长度和操作数在