学习笔记
分块思想
分块就是将一个序列分段来维护,比如有一个下标为 $[1,n]$ 的区间,我们将其分成几个大块每块大小为 $B$(当然未必是恰好,末尾可能会是一个小块),那么就有 $\lceil\frac{n}{B}\rceil$ 块。一般我们希望 $B$ 和 $\lceil\frac{n}{B}\rceil$ 差不多大,所以常取 $B=\sqrt{n}$ 。
我们能得到 / 需要维护的信息:
- 下标 $i$ 的元素属于第 $(i-1)/B+1$ 块。
- 块 $i$ 的左边界元素的下标为 $(i-1) \times B+1$,右边界元素下标为 $i\times B$ 。
- 维护块 $i$ 的某种属性,比如可以用 $\text{delta}[i]$ 表示块 $i$ 中的所有元素的一个偏移量,比如用 $\text{sum}[i]$ 表示块 $i$ 中所有元素的和,等等...也可以用数据结构(树状数组,
set
,vector
)来维护每一个块的某些属性。
任取一段区间 $[l,r]$,一般会由中间的大块以及两边的小块构成。
- 左侧小块的下标区间为:$[l,\text{belong}[l]\times B]$
- 右侧小块的下标区间为:$[(\text{belong}[r]-1)\times B+1,r]$
- 中间大块的块编号区间为:$[\text{belong}[l]+1,\text{belong}[r]-1]$
分块处理区间问题的思路就是小块暴力,大块使用维护的属性,尤其注意需要特判 $[l,r]$ 属于同一块的情况。
预处理模板
- $\mathrm{belong}[i]$ 表示下标 $i$ 所属于的块编号。
- $B$ 表示每一块的大小,$sz$ 表示一共有多少块。
- $\mathrm{L}[i],\mathrm{R}[i]$ 分别表示块 $i$ 的左闭边界和右闭边界。
|
|
例题
数列分块入门 9 题
数列分块入门 1(区间加法,单点求值)
每块维护一个加法标记 $\mathrm{delta}[i]$ 即可。
|
|
数列分块入门 2(区间加法,询问区间内小于某个值 $x$ 的元素个数)
每块维护加法标记,并用一个有序 vector
维护。区间加法操作,大块打标记,小块暴力修改以后需要重构其所在大块的有序 vector
。查询操作大块二分,小块暴力。
|
|
数列分块入门 3(区间加法,询问区间内小于某个值 x 的前驱(比其小的最大元素))
每块用一个有序 vector
维护,查询操作大块二分,小块暴力求小于 $x$ 的最大值,与大块二分得到的结果取 $\max$。
|
|
数列分块入门 4(区间加法,区间求和)
维护每块的和以及加法标记。
|
|
数列分块入门 5(区间开方,区间求和)
由于数据范围可知,每个数开方几次就会变成 $1$ 或者是 $0$(初始就是 $0$ )。那么只需要对每一个大块打一个标记,表示该大块是否只含有 $01$,这样的大块就不需要开方了。同时维护每一个大块的和 $\mathrm{sum}[i]$。查询区间和的操作不变。区间开方时,小块暴力开方,大块首先判断是否需要开方,如果不需要就跳过,如果需要就暴力开方,同时更新 $\mathrm{sum}[i]$ 的值,如果全部化成了 $01$ 就更新标记。
|
|
数列分块入门 6(单点插入,单点询问)
每块用 vector
维护,每次插入或查询根据遍历每个 vector
块大小来定位插入位置所在块。如果插入后,该块大小到达一个上限,比如 $5$ 倍的最初设置块大小,就重新分块。
|
|
数列分块入门 7(区间乘法,区间加法,单点询问)
和线段树同时维护区间和以及区间乘法做法类似,对每个大块维护两个懒标记 lazy_mul[i]
和 lazy_add[i]
,表示块 i
的所有数的实际值,都在原来的基础上先乘以 lazy_mul[i]
再加上 lazy_add[i]
。题目中的两个操作可以统一为:某个区间内的所有数需要先乘以一个数再加上一个数字(加法操作转化为先乘以 1
再加法,乘法操作转化为先乘法再加上 0
)。对于大块 i
而言,现在要让其中所有数先乘以 mul
再加上 add
,只需要修改懒标记,更新方式如下:
|
|
对于小块,那么进行暴力修改原数组即可。在此之前,必须先将小块所属于的大块 rebuild
,即将 $2$ 种标记的效果执行到原数组上,此后再进行暴力修改该数组,进行乘法和加法操作。最后,注意各种中间过程取模。
|
|
数列分块入门 8(区间询问等于一个数 c 的元素,并将这个区间的所有元素改为 c)
对每块维护一个 st[i]
,true
则表示大块中的所有元素值相同,此时 val[i] = x
表示第 i
块所有元素均为 x
。小块暴力枚举,并重构其所属大块,对于大块直接看标记计算答案,如果 st[i]
为 false
则暴力枚举块内元素,计算答案,最后更新该大块的 st[i]
为 true
即可。
|
|
数列分块入门 9(区间的最小众数)
区间 $[l, r]$ 的众数,一定是在两边的小块中出现的数,或者是中间连续的大块区域的众数,那么待选答案就有 $O(\sqrt{n})$ 个,对于每一个待选答案 $x$,我们需要求它在 $[l, r]$ 区间出现的次数,来确定最终答案。
有两种做法,$\texttt{Solution 1}$ 是预处理一个前缀和 $s[i][j]$ 表示前 $1\text{~}i$ 块中 $j$ 出现的次数,小块暴力统计,大块差分。$\texttt{Solution 2}$ 是用 vector
存每一个数 $x$ 出现的所有下标。然后在对应的 vector
中二分查包含在 $[l, r]$ 区间中的下标个数。
对于中间连续的大块区域的众数,可以预处理 $f[i][j]$ 表示第 $i\text{~}j$ 块的众数。枚举每一块 $i$,然后枚举 $j$ 从该块的左边界 $\text{L}[i]$ 一直枚举到 $n$,用哈希表统计一下出现每个数出现次数,同时维护答案 $res$,然后每当 $j$ 到达某一块的右边界即 $j == \text{R}[\mathrm{belong}[j]]$ 时候记录答案 $res$ 到 $f[i][\text{belong}[j]]$ 中,表示 $i\text{~}\text{belong}[j]]$ 块的众数为 $res$ 。预处理 $f[i][j]$ 的复杂度为 $O(n\sqrt{n})$ 。
块大小取 $B = \frac{n}{\sqrt{m \times \log_2{n}}}$ 。
$\texttt{Solution 1}$
|
|
$\texttt{Solution 2}$
|
|
Codeforces785E Anton and Permutation
题意:动态逆序对问题。给定一个初始状态为 $\text{1~n}$ 的排列,每次操作交换其中的两个数,并输出操作后当前序列的逆序对个数。
解法:qwq 咕咕咕。
|
|
AcWing263 作诗
|
|