根号分治
概述
-
根号分治,是一种对数据进行分治的分治方式。
-
具体来说,如果所要求进行的过程满足满足大点、小点(一般以根号为分界,因为这样复杂度最平衡)可以使用不同的方式处理,则可以考虑使用根号分治。一般常见的有两种情况:
-
根号以下的数据的种类很少,可以全部维护之;根号以上的数据,直接暴力的复杂度可接受。典型代表是质因数分解,此时也许可以叫数论分治。
-
和上面刚好反过来。典型代表是对图按度数分治。当然事实上说两者一样也是对的,毕竟根号分治本质上就是把两种暴力拼到一起。
-
-
一定程度上,根号分治也是一种数据分治,只不过不是数据点分治罢了(笑)。
P3396 哈希冲突
-
题意:给定序列 \(a_{1\sim n}\),有 \(m\) 个操作,为单点修改或求 \(\sum\limits_{i\bmod p=x} a_i\)。
-
数据范围:\(n,m\leqslant 1.5\times 10^5,p<n\)。
-
注意到传统方法,譬如线段树和分块,都不能很好地处理 \(\bmod p=x\) 的和,究其原因是 \(p\) 太多了,不同的 \(p\) 之间又互相难以复用。
-
但观察到对于大数即 \(\geqslant \sqrt{n}\) 的 \(p\),直接暴力的复杂度是 \(O(n\sqrt{n})\)。
-
于是考虑根号分治,小数只有 \(O(\sqrt{n})\) 个,修改时直接暴力修改。
-
总复杂度 \(O(\sqrt{n})\)。
P3939 数颜色
-
题意略。大抵我就是数据结构学傻了吧...
-
考虑根号分治,小的用 set,大的用 ta(一开始还想着分块结果发现这里 ta 更优)。\(O(n\sqrt{n}\log)\)。
-
发现小的部分复杂度很高,不太美妙。考虑换链表,去一个 \(\log\),修改暴力修就可以。\(O(n\sqrt{n})\)。
-
注意到这是排列链表,不妨乱搞一下。维护出现位置行不行?好像可以,然后直接二分...那我要链表干啥?\(O(n\log)\)。
-
主席树。看起来不卡空间。\(O(n\log)\)。
-
差分,然后 cdq。\(O(n\log)\)。
-
显然我选直接二分。。。
P1989 无向图三元环计数
-
题意略。这是非常经典的一道根号分治了...但我要先谈一个邪道做法。
-
bitset!暴力没有 bitset 怎么行!容易想到一个 bitset 维护连通性,然后枚举每条边把两个端点的出边与起来 count 一下的做法,\(T=O(\frac{nm}{w})\),然而发现空间复杂度为 \(O(n^2)\),开不下。
-
怎么办?暴力呗!根号分治,大度点开 bitset,小度点直接暴力枚举出边处理之,\(T=O(\frac{nm}{w}+m\sqrt{m}),M=O(n\sqrt{m})\)。bitset 的常数比较小,所以是可以随便乱搞过去的。
-
当然啦,我们不提倡这个...毕竟三元环计数的正解非常妙,学一下。
-
核心思路:将环按照我们希望的方式定向。
-
注意到上面的做法中每个环会被统计三次,则如果我们能把环变成有向的(当然必须是等价变换),在这一过程中人为限制大度点的出度,则问题解决。
-
具体地,规定每条边由度小的点指向度大的点。若同度,则由编号小的点指向编号大的点。
-
发现这样的生成图一定是无环的,毕竟都严格全序了,原图上所有环现在都是一个角+一条边。考虑枚举角的第一个边 \(u\to v\),然后对每个 \(v\to w\) 检查是否有 \(u\to w\)。
-
检查通过预标记实现。这一做法的复杂度一定是正确的,因为其复杂度可以变换为 \(\sum\limits_e out_{e_t}\),而 \(out\leqslant O(\sqrt{m})\),证明如下:
-
对于小度点,显然生成图上度不增。
-
对于大度点,度数不小于它的点只有 \(O(\sqrt{m})\) 个。
-
-
综上,复杂度为 \(O(m\sqrt{m})\),比 bitset 更优且更优雅。
CF444D DZY Loves Strings
-
题意略。
-
乍一看没什么思路,不妨从暴力想起。一个显然的暴力就是枚举出现次数少的串的出现,在出现次数大的串的出现序列上二分来计算答案。
-
这一做法的问题主要有两个:第一,不知道出现;第二,会 T。
-
显然 KMP 这种 \(O(|P|+|T|)\) 的求出现算法没机会,考虑 AC 自动机。我们知道 AC 自动机可以求每个模式串在文本串中的出现次数,但位置不太好办。考察其过程,发现本质上是 dfs 完之后做树形 DP,之所以位置不好办是因为要处理位置复杂度就变成了 \(\sum\limits_{i\in path} dep_i\),其中 \(path\) 为 dfs 时经过的路径的可重集合,但这里 \(dep\leqslant 4\),故可以直接做。
-
事实上并不需要这么麻烦。上面的 \(dep\leqslant 4\) 的实质不就是串长嘛,则直接枚举左端点 substr 创过去就可以了。
-
设法构造一下 \(||\geqslant \sqrt{n}\)(记 \(n=|S|\))的串最多有多少个吧,将 \(S\) 分为 \(\sqrt{n}\) 块,让串在每块中都出现一次,则一块中至多有 \(O(\sqrt{n})\) 个不同子串是输入串。性质不错,考虑根号分治。
-
对于出现次数 \(\leqslant \sqrt{n}\) 的串,暴力枚举其出现,对每个相关询问在对应串的出现上二分算答案,每个询问至多被处理 \(\sqrt{n}\) 次,复杂度为 \(O(q\sqrt{n}\log)\)。
-
此时只剩下两个串出现次数都 \(>\sqrt{n}\) 的询问,考虑预处理,即关于第一个串在 \(S\) 上扫一遍,暴力维护包含 \(T_{i\to i+x}\) 和第一个串在 \(i\) 左/右出现的子串的最小长度,\(O(n\sqrt{n})\)。
-
从而可以放弃上面的二分,把大部分事情交给预处理,对都是小次数串的直接双指针。\(O(q\sqrt{n})\)。
CF1039D You Are Given a Tree
-
题意略。差点被题解骗了以为可以整体二分...然而直接整的话是 \(O(n^2\log^2)\) 的...
-
本题其实比较数论分治。很容易注意到类似数论分块的性质,考虑对于固定的 \(k\) 怎么求解,发现其是一个赛道修建的退化版(不是边独立而是点独立),不用配对所以单轮 \(O(n)\)。
-
那么解很显然了,阈值以下的暴力求,阈值以上的二分答案检验可行的最大边数。显然对每个都二分是不可接受的,故可以考虑使用整体二分实现(只有 \(\frac{n}{B}\) 条不同的链,链长为 \(O(\log \frac{n}{B})\))但不优美,不妨利用单调性,首先对最小者计算答案,得到答案后二分边数一次决定下一个点在哪里,于是复杂度为 \(O(\frac{n}{B}\log)\),稍微平衡一下,得到 \(B=\sqrt{n\log}\),总复杂度为 \(O(n\sqrt{n\log})\)。
P5901 [IOI2009] regions
-
题意略。审完之后就觉得是一个很麻烦的根号分治...
-
考虑在线线段树合并,求得 \(sum_{i,c}\) 表示 \(i\) 的子树中颜色 \(c\) 的有多少种。这个 \(\log\) 真的好烦啊...然而空间根本开不下...这部分复杂度 \(O(n\log n)\)(直接认为颜色数和 \(n\) 同阶算了)。
-
将色分为小色和大色。对于小色,如果 \(c_1\) 是它,直接上去询问,\(O(nB\log)\);对于大色,转而考虑反演,对每个点维护它的祖先中每个大色有多少个点,过程中对对应大色贡献答案,\(O(n\frac{n}{B})\)。
-
显然这个 \(\log\) 我们不喜欢,\(\log\) 倒是无所谓但是线合很麻烦啊,数据结构是脑子的平替,不能对标脑子。我们考虑将询问离线,把大色相关询问按上面那样处理,小色相关询问按 \(c_2\) 排序,对每个 \(c_2\) 跑一遍,直接整显然复杂度还是不对是 \(O(n^2)\),但我们可以转而考虑树剖,区间修改到根即可,复杂度为 \(O(n\log^2+nB\log)\),至少比在线线合好不少。
-
平衡一下复杂度,得到 \(O(n\sqrt{n\log})\)。行吧行吧~
-
p.s.实践表明大色部分不知道为什么跑得特别慢,这里平衡复杂度不如不平衡。奇怪...
CF103D Time to Raid Cowavans
-
题意略。不管怎么看都是很典的根号分治吧?
-
首先对于 \(k\geqslant B\),直接暴力之。对于 \(k<B\),预处理即可,乍一看有 \(O(n)\) 个 \((k,t)\) 对还要枚举 \(t+xk\) 不太对,然而实际上对每个 \(k\),复杂度为 \(O(k\times \frac{n}{k})\),故复杂度为 \(O(nB)\)。啊?\(\geqslant k\) 的 \(t\)?差分啊。
-
总复杂度为 \(O(n(B+\frac{n}{B}))\),也没啥操作空间,直接 \(\sqrt{n}\) 了事。\(O(n)\) 的空间是不可能被卡的。为什么 \(O(n)\)?允许离线的啊。对每个小 \(k\) 单独做一遍不就好了。
P5309 [Ynoi2011] 初始化
-
题意略。
-
考虑直接模仿上一道题,根号分治,大的暴力,小的加到对应的剩余系上。注意到询问不太好搞。
-
暴力修的内容线段树维护一下就好了吧。也可以树状数组。考虑剩余系上的东西,枚举模数 \(x\),应该是把整个剩余系从 \(l\bmod x\) 开始遍历了不知道多少遍然后剩一段,故我们可以对每个剩余系开一个 ta 来维护之。
-
故复杂度分别为 \(O(\frac{n}{B})\),\(O(\log)\) 和 \(O(\log+B\log)\),操作一下可以把 \(\log\) 平衡进去。然而似乎还是可能会被卡...
-
看了一眼题解,果然被卡了。考虑更高效的算法,把 \(\sqrt{\log}\) 去掉。众所周知整个 \(\sqrt{}\) 生态和 \(\log\) 生态就不搭,把 ta 改成序列分块试试。
-
复杂度变成 \(O(\frac{n}{B})\),\(O(B)\) 和 \(O(B+\frac{n}{B})\)。果然,这样好了不少,就是处理系的贡献时细节比较多。事实上我们可以放弃在系上开 ta,直接在系上维护前后缀和即可,复杂度不变,毕竟系的长度这么短。
-
p.s.因为出题人试图卡各种假做法,小系特别多。考虑调块长,毕竟我们的小系常数特别大,我取 \(n^{0.395}\) 过的。
P5355 [Ynoi2017] 由乃的玉米田
-
题意略。
-
前三种操作见莫队-莫队配合 bitset-小清新人渣的本愿。我们只谈第四种。
-
根号分治。对 \(>\sqrt{n}\) 的,暴力之,反正我们手里有每个数字是否出现的 bitset。
-
对 \(\leqslant \sqrt{n}\) 的,借用一下线段树求出现那边的手法,即对每个 \(x\leqslant \sqrt{n}\),维护一个 \(pre_i\) 表示满足 \(\frac{a_i}{a_j}=x\) 或 \(\frac{a_j}{a_i}=x\) 的,\(\leqslant i\) 的最大 \(j\)。显然这容易单次 \(O(n)\) 做到。
-
乍一看好像得整个 ST 表,会带 \(\log\)...但事实上因为 \(pre_i\leqslant i\),我们可以强制 \(pre_i'=\max(pre_i,pre_{i-1}')\),然后判一下 \(pre_r'\geqslant l\) 就好了。\(O(n(\sqrt{n}+\frac{n}{\omega}))\)。
-
注意有点卡空间,\(O(n\sqrt{n})\) 个 int 有点开不下,还是把对阈值以下的询问离线下来挨个整吧。