【算法学习】分块算法入门

分块算法入门

所谓分块算法,就是讲一个序列分成若干块,维护块内的信息。为了保证一定的时间复杂度,所以对于一个 n 个元素组成的数组,将其分为 n 块,每块也有 n 个元素。所以一般分块算法的复杂中都带有根号。

对于一个暴力的区间修改问题,方法自然是对于区间的每一个元素都暴力修改,这样的话时间复杂度是明显不够优秀的。所以引入了分块的算法,分块相当于一种优雅的暴力

如何写分块(一)

维护内容

在分块中,我们需要维护的基本内容有:总的元素个数 n ,每一块的个数 b l o c k ,块的总数目 n u m ,这块左端点的位置 l [ i ] ,这块右端点的位置 r [ i ] ,元素 d a t a [ i ] 具体属于那一块,根据具体情况,可以适当修改。

下面就对上面需要进行预处理的一一解释。

每一块的个数

这个就很简单,根据分块的思想,每一块要维护 n 个元素,故每一块的个数 b l o c k = ( n )

块的总数目

总共分为 b l o c k 个块,那么每一块就有 n u m = n b l o c k 个元素,这时候要注意是否能整除,若可以则恰好分完,否则 n u m + 1

块的左右端点位置

如果我们求出了每一块的左右端点,那么也就相当于计算出了每一块所维护信息的左右区间,由于每一块有 b l o c k 个元素,我们这里设第 i 块所维护的区间为 [ l [ i ] , r [ i ] ] ,则有如下的式子:

l [ i ] = ( i 1 ) b l o c k + 1 , r [ i ] = i b l o c k

这样便可以求解出每块所维护的区间。

每个元素所属于的块号

自然我们想到用元素下标 i 去除以每块的个数 b l o c k ,这样的话,当 i = b l o c k 的时候显然是不正确的,故稍作修正 b e l o n g [ i ] = i 1 b l o c k + 1 即可。

完整代码

int n,m,num,block;
int l[nmax],r[nmax],belong[nmax];
void build(){
    block = sqrt(n);
    num = n / block; if(n % block) num++;
    for(int i = 1;i<=num;++i)
        l[i] = (i-1) * block + 1,
        r[i] = i * block;
    r[num] = n;
    for(int i = 1;i<=n;++i) belong[i] = (i-1) / block + 1;
}

如何写分块(二)

思维的转换

上面的一些基本的内容,只是为了我们更快的维护其他的数据,以达到题目的要求,如果有线段树的基础,你可以把分块理解为既加强又退化的线段树。

为什么这么说呢?

在我个人看来,线段树相当于对原数组不断进行分块,直到最后不可再分。由于一块分2块,2块分4块,这样的性质保证了复杂度在 l o g 2 n 的数量级上。而分块只分一次,每块 n 个元素。

由于线段树所分块太多,所以在面对不符合区间加法的时候,就会显得十分无力。对于分块,可以采用部分暴力部分利用块内信息来统计。

由于我目前所做分块题目比较少,以上只是一些个人的看法,也希望大家可以留言交流。

如何训练

这里推荐黄学长的 「分块」数列分块入门1 – 9 by hzwer,做题地址在LOJ

黄雪长的博客有详细的解法和代码。

To be continue.

猜你喜欢

转载自blog.csdn.net/pengwill97/article/details/80900930