优雅的暴力——分块算法初探

   如果给你一个数列\large a(n),已知它有1e6项,现在给你若干个指定区间,需要你分别求出它的区间和,你会怎么做?倘若用朴素算法(直接for循环一个一个加),那么只要区间数一多,区间范围一大,立马就会超时。所以我们需要一个新的算法来针对性解决这种问题。

  分块查找(Blocking Search)又称索引顺序查找。它是一种性能介于顺序查找和二分查找之间的查找方法。

   类似于线段树,它可以对一段数据中单个数或区间进行修改或访问。譬如引例,对于这样的一个数列,我们可以将其分为多段,假设其有x项,那么为了最低的时间复杂度,一般情况下是将其一个子段的长度设定为\large \sqrt{x}(可以根据均值不等式算出),最后一段不满这个长度的子段亦单独设定为一段,然后在处理区间的时候就可以直接利用子段的相关总数据(比如总和)来解决问题。

  这个算法要怎么实现呢?

  1. 既然要分块,那么我们必须得有记录“块”的下标的一个专用数组便以查询和确立边界,block_number[amount],这是分块的核心。其余的数组可以依据题意定义,如果要进行区间修改和区间查询的话,我们需要一个记录区间和的数组sum[amount]和一个懒惰标记数组laziness_tag[amount].
  2. 在使用分块解决问题之前还需要一点准备工作,通过循环给原数列输入数值,块下标数组的值由公式\large \frac{i-1}{BlockLength}+1得到,(BlockLength为\large \sqrt{ArrayLength}),同时凭据"块下标"累加当前块内\large a[i]的值来得到区间和\large Sum[BlockNumber[i]].
  3. 接下来的搜索分三个部分,首先是左部非完整块逐一搜索,左部的区间为[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];
         //由于把整块看做一个整体,那么懒标记也要乘以相应的倍数
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_43874261/article/details/87652605