数列分块!!!

世界上怎么会有分块那么可爱的思想!!!

一、概念

分块,你可以说它是数据结构,也可以说是一种思想。

而数列分块呢,就是将一段序列分成许多块,分别维护每块的信息,来求出一段区间的信息(比如最大值,区间和等等)。

可以用下面的图片来理解:

Emm,搞错了:

对于区间询问 + 修改的问题,我们就可以使用分块的思想,在区间覆盖的整块部分直接修改整块的信息,而不完整块直接暴力处理(太可爱了,真的很好写!)

设块长为 \(b\),该算法时间复杂度一般为:\(O(B + \frac{N}{B})\),当 \(B = \sqrt{N}\) 时取得最小。

二、写法

1.处理每块左右端点

for(int i = 1;i <= n;i ++){
	bel[i] = (i - 1) / len + 1;//bel[i]表示属于那个块,len为块长
}
for(int i = 1;i <= num;i ++){
	l[i] = (i - 1) * s + 1;
    r[i] = i * s;
}//l[i] 为一个区间的左端点,r[i]为一个区间的右端点,num为块的数量
r[num] = n;
// 注意,最后一块可能不完整!

2.修改操作

  • 区间修改

和线段树类似,分块也可以有 lazy_tag,对于直接修改整块,可以直接打 Tag,当查询 or 修改到该块的部分时再下放标记。

区间修改一般是这么一个框架:

if(bel[l] == bel[r]){
    pushdown(bel[l]);
	暴力执行
}else{
	pushdown(bel[l]);
	for(int i = l;i <= R[bel[l];i ++){
    	暴力
    }
    pushdown(bel[r]);
    for(int i = L[bel[r]] ;i <= r;i ++){
    	暴力
    }
    for(int i = bel[l] + 1;i <= bel[r] - 1;i ++){
    	块信息整体修改
    }
}
  • 单点修改

直接暴力修改单点,再对所属块,重新记录信息。

Link:

a[x] = ···;
for(int i = L[bel[x]] ;i <= R[bel[x]];i ++){
	val[bel[x]] = ···;
}

3.查询操作

  • 区间查询

区间查询与区间修改差不了太多:

if(bel[l] == bel[r]){
    pushdown (bel[l]);
    暴力统计
}else{
    pushdown (bel[l]);
    for(int i = l;i <= R[bel[l];i ++){
        暴力统计
    }
    pushdown (bel[r]);
    for(int i = L[bel[r]] ;i <= r;i ++){
        暴力统计
    }
    for(int i = bel[l] + 1;i <= bel[r] - 1;i ++){
        块信息整体统计
    }
}
  • 单点查询

先将所属块标记下放,直接获取 \(Ans\)

Link:

pushdown(bel[x]);
ans = a[x];

二、常见错误

  • 在最后一块的右端点没有设为 \(n\)

  • 在查询时忘记下放标记

  • 标记改变错误

三、技巧 / 套路

  • 对于种类数的统计,由于块的数量只有 \(\sqrt{N}\),于是乎可以直接开下 \(cnt[x][val]\) 的数组,直接统计。 link: P3313 [SDOI2014] 旅行
posted @ 2023-07-16 21:16  固态H2O  阅读(5)  评论(0编辑  收藏  举报  来源