根号算法

根号分治

就是利用根号平衡的思想,对于不同的数据用不同的维护方法。本质是数据分治

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\),维护一个前缀和,每次二分零点更新答案。

代码不是很好写的感觉。


P4117CF896E

第二分块。看起来很不对实际上是对的复杂度。

考虑分块。并查集维护值,对于散块,直接重构。对于整块,若块内最大值 \(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'\),贡献为

\[\sum_{i=r+1}^{r'} f(i,[l,i-1])=f(i,[1,i-1])-f(i,[1,l-1]) \]

前半部分东西可以扫一遍预处理,对于后半部分,看成一段区间对一段前缀的贡献。
考虑将 \(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

维护最远距离即维护区间 最大值-最小值,区间最大值最小值不可撤销。

回滚莫队,将区间分为当前块内和当前块右两块,两块内分别维护最大值和最小值。

posted @ 2025-01-01 22:46  Wonder_Fish  阅读(34)  评论(0编辑  收藏  举报