根号分治
就是利用根号平衡的思想,对于不同的数据用不同的维护方法。本质是数据分治
P8572
突然想起来了很久前做的神秘水题。\(O(qk)\) 和 \(O(n^2k+q)\) 的暴力都不难想,但是第一种在 \(k\) 大的时候会似,第二种在 \(n\) 大的时候会似。
题目保证 \(nk \leq 5\times 10^5\),也就是说 \(\min(n,k) \leq \sqrt{5\times 10^5}\),讨论一下 \(n,k\) 的相对大小分别用两种方法做就行了。纯纯数据分治。
CF797E
很容易想到两种暴力,一种是对于确定的 k,从后向前递推 \(f[i]\) 表示从 i 开始需要跳几次,另一种是直接每次询问暴力跳。
发现第一种的问题在于 \(k\) 太大的时候复杂度 \(O(nk)\) 会炸,第二种问题在于 \(k\) 太小的时候要跳的次数太多。
考虑根号分治,对 \(k \leq \sqrt{n}\) 的预处理答案,对于 \(k > \sqrt{n}\) 的直接跳,时间复杂度 \(O((n+q)\sqrt{n})\)。
体现了根号分治就是数据分治。
CF1039D
对于确定的 k,可以 \(O(n)\) 贪心做。
对于小的 k,直接做。对于大的 \(k\),发现答案会比较小,枚举每个答案,二分出它对应的 \(k\) 区间。
贪心的过程类似赛道修建,都是从下向上在父节点处拼接链,但有一些区别。这题每个点只存在于一条链中,也就是要么是两个儿子的链在这里接起来,要么一个儿子连到他再向上接,可以发现第一种不劣,所以能接第一种就接第一种即可。
另外有题解提到答案不超过 \(\lfloor \frac{n}{k} \rfloor\),而 \(\lfloor \frac{n}{k} \rfloor\) 的取值数量是根号级别的,所以答案种类数是根号级别的。不过复杂度一样。
CF1580C
每个车的周期是 \(x_i+y_i\),看到周期,同余之类的可以想想根号分治。
考虑维护一个数组表示每天维修的车数,插入删除操作暴力区间加。可以使用差分做到 \(O(\frac{n}{x_i+y_i})\) 修改,
对于 \(x_i+y_i\) 比较小的车,需要修改的段数太多了,但是对于周期相同的车,容易一起算。对每种周期记录周期中 \(\bmod (x_i+y_i)\) 每个余数的位置有多少车需要维修。
阈值取 \(\sqrt{m}\),复杂度 \(O(m\sqrt{m})\)。
P5309
有一点像根号分治典题哈希冲突。
考虑修改操作与原序列分开算。
首先对于 \(x>\sqrt{n}\) ,修改的位置不会太多,直接分块维护前缀和(用分块是为了平衡修改和查询的复杂度)。
对于 \(x\leq \sqrt{n}\),维护 \(\bmod x \equiv y\) 的位置的标记和的前缀和,修改的时候单点修改相当于前缀和的区间修改,直接暴力做,询问的时候遍历一遍所有小的循环节,可以 \(O(1)\) 算出 \([l,r]\) 内每个余数出现的次数(细节较多,轻微卡常)。
分块
不想写,先鸽(悲
P5356
用复杂度不是很对的方法过去了。
看到数据范围,考虑到这是 Ynoi,就可以想分块了。(事实上对于 \(10^5\) 的范围,想不到好的数据结构时可以考虑分块,也许会简单一些)
设块长为 \(B\)。
对一个块内的数都加上 \(k\),不会改变数的相对大小,可以通过打标记实现。对于散块,每次只会改 \(O(1)\) 个,暴力重构即可。修改时修改的位置之间和不修改的位置之间都保持有序,可以归并排序,因此修改复杂度 \(O(B+\frac{n}{B})\)。
查询时,考虑二分答案,散块内暴力统计,整块已经排好序,直接再二分,单次复杂度 \(O(\log V(B+\frac{n}{B}\log B))\)。
实际块长取 250 左右时跑的比较快,我也不知道为什么。
P4168
典题了属于是。即强制在线询问区间众数。
考虑分块,区间 \([l,r]\) 的众数一定是 \([l,r]\) 中所有整块的众数或者是在散块中出现过的数。预处理每个每个整块构成的区间的众数和每个数关于块的前缀和,询问时将整块区间众数和散块中的每个数作比较找出众数。
P3203
我一个没学过 LCT 的人都一眼 LCT,但是我不会 LCT。但是这题的分块做法非常简单!
首先会想到维护 \(f_i\) 表示从 \(i\) 会弹到哪 \((f_i>i)\),构成了一个树结构,每次修改相当于把一个子树整个搬走。
如果要维护每个点最终到哪,那么修改涉及到的点是 \(O(n)\) 的。如果每次暴力跳,跳的步数是 \(O(n)\) 的。于是会有一种想法,记录一个点向后跳一定范围到达的点去平衡复杂度。
显然阈值取 \(\sqrt n\),分块,对每个块维护块内每个点跳出块到达的点,不难发现修改只与块内有关,查询最多跳块的个数次。
P3992
不难发现最优情况下,是把加油站和车分别排序后按顺序对应。但每次排序计算显然过不去,排序操作也不太好优化。
考虑转化贡献,把所有车和加油站的位置排序,得到 \(x\) 轴上 \(O(n)\) 个点。对于每个相邻两点见的线段,考虑会有多少车经过。设第 \(i\) 个点和前一个点的距离是 \(d_i\),前 \(i\) 点中加油站个数减车数是 \(c_i\),则答案为 \(\sum {|c_i|}\cdot d_i\)。每次修改是将一段 \(c_i\) 区间 \(+1\) 或 \(-1\)。
考虑分块。每个块块内排序,散块暴力重构,整块打标记。贡献可以写成 \(\sum c_i\cdot d_i - 2 \cdot \sum_{c_i<0} c_i\cdot d_i\),维护一个前缀和,每次二分零点更新答案。
代码不是很好写的感觉。
P4117(CF896E)
第二分块。看起来很不对实际上是对的复杂度。
考虑分块。并查集维护值,对于散块,直接重构。对于整块,若块内最大值 \(mx>2x\),就将 \(\leq x\) 的数加上 \(x\),并给块打上 \(-x\) 的标记,否则直接将 \(>x\) 的数 \(-x\)。修改数都用并查集实现。
这样保证了每次会将块内值域缩小 \(x\)。势能分析法可的得复杂度是 \(O(V \sqrt n)\) 乘并查集常数的,很神奇。
莫队
CF617E & P4462
莫队板题。首先把区间异或和转为两个异或前缀和的异或,然后维护一个区间内异或前缀和值 为 \(i\) 的数个数。
P4396
莫队 + 值域分块
发现对于一些没法 \(O(1)\) 插入删除的东西,如果使用 \(\log\) 数据结构,整体复杂度就变为了 \(O(n\sqrt n \log n)\),无法接受。
但是其实对于所有询问,修改操作有 \(n \sqrt n\) 次,但是询问操作只有 \(n\) 次,所以我们考虑继续运用根号平衡的思想,找一些 \(O(1)\) 修改,\(O(\sqrt n)\) 查询的方式,即值域分块。
但是有的时候我们有不同的需求,比如有时候二次离线需要 \(O(\sqrt n)\) 修改,\(O(1)\) 查询,此时换一种值域分块的方式,见 P5501 的二离部分,要注意区分。
P7708
较为复杂的莫队。
咕
莫队二次离线
有的时候我们没有办法 \(O(1)\) 实现插入删除,也没法值域分块(比如每次插入删除会考虑一个点对一个区间的贡献),所以我们需要把这些点对区间的贡献的询问离线下来再做,即莫队二次离线。
每一次端点移动可以看为一堆点对于一些区间的贡献,一般来说这种东西具有可减性。
记 \(f(i,[l,r])\) 表示点 \(i\) 对 \([l,r]\) 的贡献,假设某次移动右端点由 \(r\) 移动到 \(r'\),贡献为
前半部分东西可以扫一遍预处理,对于后半部分,看成一段区间对一段前缀的贡献。
考虑将 \(g([r+1,r'],[1,l-1])\) 挂到 \(l-1\) 上,然后再从前往后扫加入每个点,然后暴力回答挂在这个点上的询问,由于莫队端点移动的总长度是 \(O(n\sqrt n)\) 所以可以接受。
P5501 中二次离线部分依然不能 \(O(1)\) 处理,此时发现添加点(即修改操作)是 \(n\) 次,查询是 \(O(n\sqrt n)\) 次,此时我们需要修改根号,查询 \(O(1)\) 的数据结构。
注意:
-
贡献的符号
-
有时候要注意贡献的含义,比如逆序对,是查区间内比某个数大还是比某个数小。
P4887
莫队二次离线模板。
回滚莫队
有的时候我们维护的信息不支持删除,比如最大值最小值,这个时候需要一种不用删除的莫队,即回滚莫队。
回滚莫队的思想是,把左端点在一个块内的询问按右端点从左到右排序(所以不能奇偶排序优化了,悲),每次把左端点放在块尾,然后向右移动右端点,遇到询问就暴力左移左端点,询问完后再撤回,把左端点放回块尾。
看上去很暴力,但是复杂度是对的。
P5906
维护最远距离即维护区间 最大值-最小值,区间最大值最小值不可撤销。
回滚莫队,将区间分为当前块内和当前块右两块,两块内分别维护最大值和最小值。