根号分治

概述

  • 根号分治,是一种对数据进行分治的分治方式。

  • 具体来说,如果所要求进行的过程满足满足大点、小点(一般以根号为分界,因为这样复杂度最平衡)可以使用不同的方式处理,则可以考虑使用根号分治。一般常见的有两种情况:

    • 根号以下的数据的种类很少,可以全部维护之;根号以上的数据,直接暴力的复杂度可接受。典型代表是质因数分解,此时也许可以叫数论分治。

    • 和上面刚好反过来。典型代表是对图按度数分治。当然事实上说两者一样也是对的,毕竟根号分治本质上就是把两种暴力拼到一起。

  • 一定程度上,根号分治也是一种数据分治,只不过不是数据点分治罢了(笑)。

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 有点开不下,还是把对阈值以下的询问离线下来挨个整吧。

posted @ 2023-01-07 10:22  未欣  阅读(964)  评论(0编辑  收藏  举报