如果给你一个数列
,已知它有1e6项,现在给你若干个指定区间,需要你分别求出它的区间和,你会怎么做?倘若用朴素算法(直接for循环一个一个加),那么只要区间数一多,区间范围一大,立马就会超时。所以我们需要一个新的算法来针对性解决这种问题。
分块查找(Blocking Search)又称索引顺序查找。它是一种性能介于顺序查找和二分查找之间的查找方法。
类似于线段树,它可以对一段数据中单个数或区间进行修改或访问。譬如引例,对于这样的一个数列,我们可以将其分为多段,假设其有x项,那么为了最低的时间复杂度,一般情况下是将其一个子段的长度设定为
(可以根据均值不等式算出),最后一段不满这个长度的子段亦单独设定为一段,然后在处理区间的时候就可以直接利用子段的相关总数据(比如总和)来解决问题。
这个算法要怎么实现呢?
- 既然要分块,那么我们必须得有记录“块”的下标的一个专用数组便以查询和确立边界,block_number[amount],这是分块的核心。其余的数组可以依据题意定义,如果要进行区间修改和区间查询的话,我们需要一个记录区间和的数组sum[amount]和一个懒惰标记数组laziness_tag[amount].
- 在使用分块解决问题之前还需要一点准备工作,通过循环给原数列输入数值,块下标数组的值由公式 得到,(BlockLength为 ),同时凭据"块下标"累加当前块内 的值来得到区间和 .
- 接下来的搜索分三个部分,首先是左部非完整块逐一搜索,左部的区间为[left,block_number[left]*block_length](left到这块末尾的所有元素),再是右部非完整块逐一搜索,区间为[(block_number[right]-1)*block_length,right]最后中间的完整块逐一搜索[block_number[left]+1,block_number[right-1](每一块都看做整体)。
笔者实现这种功能的代码如下。
int block_length,block_number[500],sum[500],arrays[500],laziness_tag[500];
int array_length;
void setup()
{
block_length=floor(sqrt(array_length));//分块长度指标
for(int i=1; i<=array_length; i++)
{
cin>>arrays[i];
block_number[i]=(i-1)/block_length+1;//块的标号
sum[block_number[i]]+=arrays[i];//区间和
}
}
void interval_modification(int limit1,int limit2,int value)
{
for(int i=limit1; i<=min(block_number[limit1]*block_length,limit2); i++) ///左部搜索,min()的用意是若limit2在这一块内,则到limit2处结束,不然就到这一块的终点结束。
{
arrays[i]+=value;//单个值直接修改即可,不需要懒标记。
sum[block_number[i]]+=value;//不要忘记改变区间和
}
if(block_number[limit1]!=block[limit2])//左右端点不在同一块上
{
for(int i=(block_number[limit2]-1)*block_length; i<=limit2; i++) //从上一块的末尾开始右端搜索.
{
arrays[i]+=value;
sum[block_number[i]]+=value;
}
}
for(int i=block_number[limit1]+1; i<=block_number[limit2]; i++)
laziness_tag[i]+=value;//整块的值变动直接打到懒标记上即可
}
int interval_query(int limit1,int limit2)
{
int ans=0;
for(int i=limit1; i<=min(block_number[limit1]*block_length,limit2); i++)
{
ans+=(arrays[i]+laziness[block_number[i]);
//这个元素的真实值是它的原值加上块懒标记的值
}
if(block_number[limit1]!=block_number[limit2])
for(int i=block_number[limit1]*block_length+1; i<=limit2; i++)
{
ans+=(arrays[i]+laziness[block_number[i]);
}
for(int i=block_number[limit1]+1; i<=block_length[limit2]-1; i++)
{
ans+=sum[i]+block_length*laziness[i];
//由于把整块看做一个整体,那么懒标记也要乘以相应的倍数
}
}