前言
这一切都是基于「分块」数列分块入门1 – 9,也就是对其的总结。
链接
#6277. 数列分块入门 1 数列分块入门1 LibreOj 6277_Carry_hkr的博客-CSDN博客
#6278. 数列分块入门 2 数列分块入门2 LibreOj 6278_Carry_hkr的博客-CSDN博客
#6279. 数列分块入门 3 数列分块入门3 LibreOj 6279_Carry_hkr的博客-CSDN博客
#6280. 数列分块入门 4 数列分块入门4 LibreOj 6280_Carry_hkr的博客-CSDN博客
#6281. 数列分块入门 5 数列分块入门5 LibreOj 6281_Carry_hkr的博客-CSDN博客
#6282. 数列分块入门 6 数列分块入门6 LibreOj 6282_Carry_hkr的博客-CSDN博客
#6283. 数列分块入门 7 数列分块入门7 LibreOj 6283_Carry_hkr的博客-CSDN博客
#6284. 数列分块入门 8 数列分块入门8 LibreOj 6284_Carry_hkr的博客-CSDN博客
#6285. 数列分块入门 9 数列分块入门9 LibreOj 6285_Carry_hkr的博客-CSDN博客
总结
这一路做下来,我觉得分块一般分成三个部分:1)预处理;2)操作;3)询问。有些时候的询问和操作是放在一起的,比如入门8的查询之后的赋值
预处理:预处理一般处理一些block(块的长度),num(块的个数),l[i](块i的最左边的位置),r[i](块i的最右边位置),还有一个belong[i](表示第i个位置是属于哪一块的),常规的是这些,还有具体情况具体分析的,比如初始化flag标志数组为-1(如入门8)等。有些题解是没有写l,r数组的,因为可以直接表示出来,我觉得那样代码虽然短,但是不美观,有时候不太好理解,这样预处理之后可以一劳永逸了。
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;
for (int i=1;i<=num;i++)
for (int j=l[i];j<=r[i];j++)
Max[i]=max(Max[i],a[j]);
//这里拿区间最大值来举例
}
操作:操作的话大部分是以区间操作进行,除了一些特殊的,比如入门6的单点插入,单点查询,区间操作分块就起大作用了。块,分为两种:完整块,我们一般用lazy等懒惰处理,需要用的时候再去进行更新等操作;不完整块,我们在类似lazy的基础上进行,有时候需要先全部更新一下,例如入门8的flag和reset,然后再暴力修改。还有一个重要的地方,如果对于区间比较小,也就是说,不完整块只有一块,不需要从右边界再处理一次不完整块。
inline void update(int x,int y,int c)
{
if (belong[x]==belong[y])
{
for (int i=x;i<=y;i++) a[i]+=c;
return;
}
for (int i=x;i<=r[belong[x]];i++) a[i]+=c;
for (int i=belong[x]+1;i<belong[y];i++) lazy[i]+=c;
for (int i=l[belong[y]];i<=y;i++) a[i]+=c;
//这个用区间加c来举例
}
查询:查询其实也跟操作一样,对于不完整块我们暴力查询,对于完整块我们就用类似于lazy操作得到答案,比如入门9的maxn[i][j]能直接得到第i块到第j块的最小众数。我们也需要注意一下左右区间边界是否是属于同一块的情况下,避免重复。
其他:这些事总结起来的特殊情况。
1)依情况而定,是否需要取余
2)是否需要开long long
3)对于一个区间排序二分,赋值的时候需要整体将整块赋值给排序数组,然后排序
4)有些题目block不易太小,可以手动设置block=200等,不然会t
附上几张图