分块学习笔记

前言:

分块——高级的暴力。采用的主体思想是“整块打标记,散块打暴力”。能解决很多线段树,平衡树,树套树等数据结构类问题,复杂度高但常数很小,可以和一些大常数的双log甚至单log数据结构持平,且思维相对简单,缺点是码量较大,需要一定的代码能力。

上篇——静态分块

区间分块

最简单的东西,感觉也是变化最多的东西

把数组分成\(\sqrt{n}\)段,每段的长度是\(\sqrt{n}\)

分块开不开结构体都可以,一般需要对每个块维护\(st,ed\),并对每个点维护\(num\),表示该点在哪个块中。

值域分块

分块的进阶版,在区间分块的基础上对值域分块,为了保证修改和查询时正确的复杂度,一般采用前缀和优化。即设\(sum(i,u)\)代表第\(1 \sim i\)块,落在值域上第\(u\)块的总个数,再设\(val(i,u)\)代表第\(1 \sim i\)块,数值为\(u\)的总个数。

则修改和查询操作都可以分为:

\[ \left\{ \begin{matrix} 区间整块,值域整块\\ 区间整块,值域散块\\ 区间散块\\ \end{matrix} \right. \]

值域分块可以解决一些诸如带修改区间第k小等原本需要树套树才能解决的问题,赢!

下篇——块状链表(动态分块)

“动态”不是指支持带修,这个普通分块也能做,但是块状链表可以支持带插入,即在序列的第\(k\)项插入一个数值\(x\),原来后面的数集体向后平移一位。

实现很简单,先分块,把原来分好的块用链表串联起来,也就是对每个块维护nxt,指向它的下一个块。遇到插入则暴力重构整个块。

注意在同一个位置插入过多的数会导致这个块长度远大于\(\sqrt{n}\),所以需要一个\(split\)函数,当块长过长$(\ \ge 2*sqrt{n})$时,就把这个块分裂成两个。

特殊情况下也会需要一个\(merge\)函数,但大多数情况下不用,因为如果每次只删一个数,则无论如何块的个数都不会过多。

块链的写法是对每个块开一个结构体,存\(len,nxt\)和原数组,很多情况下也要存\(st\)\(mx\),具体情况具体分析。会涉及到求下标\(x\)在哪个块内,可以从头开始跳,也可以像普通分块一样维护一个\(num\)数组。

这里简单说一下个人总结出的三种块链。

单点插入,全局查询

eg.P3369

最好写的块链,这类题不涉及元素的位置,可以直接维护一个有序的序列,通常要对每个块维护\(mx\),注意插入哨兵节点。

单点插入,区间查询

eg.存储器,P4278

比较常见的块链,一般需要维护\(num\)数组和每个块的\(st\),查询正常做,插入时后面所有块的\(st++\),插入和分裂时注意维护\(num\)数组。

有时候还可以搭配值域分块食用。

区间插入,区间查询

eg.P2042

比较难写的块链,个人觉得更像一种根号版平衡树(

我们稍微魔改一下\(split\)函数,让它可以指定从某个位置把一个块分成两个,那么对一个区间的操作就可以转化为对一堆整块的操作,注意最后要\(merge\)回去,同时注意块的个数要开多一点。

同时特别说一下区间反转,转化为一堆整块之后,把每个块打个\(tag\),同时它的\(nxt\)指针都指向原来自己前面的数,然后两边特殊处理一下即可。注意块内如果维护了前后缀相关内容,则需要\(swap\)一下。单独写一个下传标记,重构整个块。

posted @ 2025-04-03 14:40  Nihachu  阅读(28)  评论(0)    收藏  举报