分块
对于分块的解释…看名字就知道了嘛,就是把问题分成一块一块的来进行处理(听起来是不是很像分治,名字也挺像)。这个算法实际上和树状数组以及线段树相似,都可以进行在一段区间上的操作,但相较于这两者而言,分块的时间复杂度要高一些,但代码要简洁一些,更方便实现和查错!!!
具体操作
首先就是对于分块的长度的约束了,那么对于分块的长度,我们最好将它分成 n \sqrt{n} n 段长度为 n \sqrt{n} n 的块,这样就能使代码的时间复杂度最低了!
那么我们就来一道例题来康康这个算法的具体过程吧!
例题
这道题就是对一个数组进行区间修改和区间求和。
对于每段区间的长度,可以得到 l e n = n len=\sqrt{n} len=n ,那么我们易得每个点所属于的块的编号即为 ( i − 1 ) ∗ l e n + 1 (i-1)*len+1 (i−1)∗len+1 。
int get(i)
{
return (i-1)*len+1;
}
接着是修改的操作。
对于每一个区间
[
l
,
r
]
[l,r]
[l,r]上的修改,我们可以分成两种情况,一种是l和r处于同一个块中,第二种是l和r处于不同的块中。
我们可以用两个数组来维护答案,sum[]维护当前块上的值的累加和,add[]维护当前块中的增加值。接下来我们就可以来进行修改。
一.对于l和r处于同一个块中。
因为每一块的长度 l e n ≤ n len\leq\sqrt{n} len≤n,所以即使是最坏的情况时间复杂度也只是 O ( n ) O(\sqrt{n}) O(n),因此我们就可以暴力地从l到r一个一个进行修改。
if(get(l)==get(r))
{
for(int i=l;i<=r;i++)
{
a[i]+=d;//d为增加的值
sum[get(i)]+=d;
}
}
二.对于l和r处于不同的块中
首先同第一种情况相同,我们可以先暴力修改出l和r所在的块(即用红色斜线标出的区域),然后对于剩下的区域(即用红色叉叉标出的区域),全部都是整块,那么我们可以整体进行修改。
if(get(l)!=get(r))
{
int i=l,j=r;
while(get(i)==get(l)) a[i]+=d,sum[i]+=d,i++;
while(get(j)==get(r)) a[j]+=d,sum[j]+=d,j--;
for(int k=get(i);k<=get(j);k++) sum[k]+=len*d,add[k]+=d;
}
那么整合一下,我们对于修改的操作便是:
void change(int l,int r,int d)
{
if(get(l)==get(r))
{
for(int i=l;i<=r;i++)
{
a[i]+=d;
sum[get(i)]+=d;
}
}
else
{
int i=l,j=r;
while(get(i)==get(l)) a[i]+=d,sum[i]+=d,i++;
while(get(j)==get(r)) a[j]+=d,sum[j]+=d,j--;
for(int k=get(i);k<=get(j);k++) sum[k]+=len*d,add[k]+=d;
}
}
接下来就是查询的操作。
实际上就和修改的步骤相似,同样分成两种情况,然后进行查找。
相信在座的都是大佬我就不详细解释了,直接上代码。真的不是我懒
int work(int l,int r,int d)
{
int ans=0;
if(get(l)==get(r))
{
for(int i=l;i<=r;i++) ans+=a[i]+add[get(i)],ans%=d;
}
else
{
int i=l,j=r;
while(get(i)==get(l)) ans+=a[i]+add[get(i)],ans%=d,i++;
while(get(j)==get(r)) ans+=a[j]+add[get(j)],ans%=d,j--;
for(int k=get(i);k<=get(j);k++) ans+=sum[k],ans%=d;
return ans%d;
}
}
代码到了这里就结束了,接下来奉上几份例题!!!
分块九题
数列分块入门1
数列分块入门2
数列分块入门3
数列分块入门4
数列分块入门5
数列分块入门6
数列分块入门7
数列分块入门8
数列分块入门9