【2025.12.13-2025.12.25】北京多校集训 —— 奇怪复杂度数据结构
【2025.12.13-2025.12.25】北京多校集训 —— 奇怪复杂度数据结构
LG9809. [SHOI2006] 作业 Homework
设 \({X=pY+q}\),于是有 \({pY\le n}\),考虑对模数 \({Y}\) 进行阈值分治:
- 若 \({Y\le B}\),则可以直接维护 \({X \bmod Y}\) 的最小值,复杂度是 \({O(nB)}\) 的。
- 否则有 \({p\le \frac{n}{B}}\),可以枚举所有 \({p}\),在 \({[pY,pY+p-1)}\) 中找到最小的数即可,复杂度是 \({O(\frac{n^2}{B}\log n)}\)。
复杂度是 \({O(nB+\frac{n^2}{B}\log n)}\),取 \({B=O(\sqrt{n\log n})}\) 可以做到 \({O(n\sqrt{n\log n})}\)。
CF1822G2. Magic Triples (Hard Version)
考虑 \({b}\) 一定是 \({a_j}\) 的一个因数,可以考虑分解 \({a_j}\) 的所有因数之后暴力枚举,复杂度瓶颈在求因数的 \({O(n\sqrt{V})}\)。
注意到 \({a_k=b\cdot a_j\le V}\),可以考虑对 \({a_j}\) 进行阈值分治:
- 若 \({a_j\le B}\),则可以直接 \({O(\sqrt{B})}\) 求出因数。
- 否则有 \({b\le\frac{V}{B}}\),直接枚举所有的 \({b}\) 计算答案可以做到 \({O(\frac{V}{B})}\)。
于是总复杂度是 \({O(n(\sqrt{B}+\frac{V}{B}))}\),取 \({B=O(V^{\frac{2}{3}})}\) 可以做到 \({O(nV^{\frac{1}{3}})}\)。由于 \({d(10^9)}\) 和 \({V^{\frac{1}{3}}}\) 同阶,因此枚举因数不是瓶颈。
LG5071. [Ynoi Easy Round 2015] 此时此刻的光辉
一个非常基础的思路是考虑到 \({10^9}\) 以下的数最多有 \({10}\) 个本质不同的质因数,因此莫队单次加删点是 \({O(10)}\) 的,总复杂度 \({O(10n\sqrt{n})}\) 遗憾离场。
考虑一个数 \({>\sqrt{n}}\) 的质因数只有一个,这样莫队复杂度降至 \({O(n\sqrt{n})}\),对于 \({\le\sqrt{n}}\) 的质因数维护前缀和即可。但这样由于 \({\sqrt{10^9}=31622}\) 以下有 \({3401}\) 个质数,空间复杂度会较高。可以退而求其次考虑到一个数 \({>\sqrt[3]{n}}\) 的质因子最多只有两个,而 \({\sqrt[3]{10^9}=10^3}\) 以下只有 \({168}\) 个质数,这样复杂度就是 \({O(168n+2n\sqrt{n})}\) 的。
考虑分解质因数的复杂度,直接枚举所有质因数复杂度是 \({O(3401n)}\) 的,可以通过,也可以使用 Pollard-Rho 算法优化。
LG5607. [Ynoi2013] 无力回天 NOI2017
先根据 \({|S\cup T|=|S|+|T|-|S\cap T|}\) 将求交转为求并。
基于大小 \({>B}\) 的集合只有 \({\frac{m}{B}}\) 个进行阈值分治可以做到 \({O(m\sqrt{m})}\),但难以进一步优化。
考虑出现次数 \({>B}\) 的元素只有 \({\frac{m}{B}}\) 个,对这个内容进行阈值分治:
- 对于出现次数 \({\le B}\) 的元素,可以记录其在那些集合中出现,当新加入一个集合后,会增加 \({O(B)}\) 个二元组 \({(x_1,x_2)}\) 表示该元素出现在 \({x_1\cap x_2}\) 中,用哈希表存储后 \({O(1)}\) 查询即可,复杂度是 \({O(mB)}\) 的。
- 对于出现次数 \({>B}\) 的元素,因为最多只有 \({\frac{m}{B}}\) 个,可以用
bitset维护每个集合中有哪些元素,这样查询只需要将两个bitset与起来求 \({1}\) 的个数即可,复杂度是 \({O(\frac{m^2}{Bw})}\) 的。
于是总复杂度可以做到 \({O(mB+\frac{m^2}{Bw})}\),取 \({B=O(\sqrt{\frac{m}{w}})}\) 可以做到 \({O(m\sqrt{\frac{m}{w}})}\)。
但是 C++ 自带的哈希表存储 \({O(mB)}\) 个数效率很低下,可以考虑到只有 \({O(m)}\) 次查询,那么可以只关心这 \({O(m)}\) 个位置的值,用指针动态分配空间即可,空间复杂度优化至 \({O(m)}\)。
同时注意到空间复杂度 \({O(\frac{m^2}{Bw})}\) 是开不下的,于是可以考虑对所有 \({\frac{m}{B}}\) 个数进行 bitset 分块,每 \({w}\) 个数跑一轮操作,这样单次只用开 \({O(m)}\) 个 unsigned long long 进行存储,空间复杂度优化至 \({O(m)}\)。
LG5386. [Cnoi2019] 数字游戏
一个自然的想法是将值域内的所有数字标成 \({1}\),其余的标成 \({0}\),答案就是区间 \({[l,r]}\) 内所有极长的 \({1}\) 的连续段长度 \({L}\) 对应 \({\frac{L(L+1)}{2}}\) 的和。可以想到用线段树维护这个内容,单点修改和区间查询都是 \({O(\log n)}\) 的,使用莫队来移动值域区间即可做到 \({O(n\sqrt{n}\log n)}\)。
想去掉 \({\log}\) 可以考虑更新次数是 \({O(n\sqrt{n})}\) 的,但查询次数只有 \({O(n)}\),现在期望给出一个 \({O(1)}\) 更新,\({O(\sqrt{n})}\) 查询的数据结构。想到分块,对每个块维护和线段树类似的信息,查询可以做到 \({O(\sqrt{n})}\)。对于 \({O(1)}\) 更新想到维护极长连续段的经典做法:对每一个连续段开头和末尾维护相互指向的指针,这样可以做到将 \({0}\) 改成 \({1}\) 后 \({O(1)}\) 更新指针,但是不支持将 \({1}\) 到 \({0}\) 的快速更新,改为使用回滚莫队可以解决这个问题。这样复杂度被平衡成了 \({O(n\sqrt{n})}\)。
LG5608. [Ynoi2013] 文化课
一个直觉是可以直接上线段树维护。
先考虑没有修改的时候如何求解:可以对每个区间维护直接计算的值,在合并的时候如果中间的符号为 \({+}\) 应该直接累加,否则两边的乘法段会产生新的贡献,应该单独计算。所以维护每个区间直接计算的值、左端点前的符号、左右极长的乘法段的值即可做到 \({O(n\log n)}\) 计算。
现在加入区间修改符号的操作:肯定需要多一个区间修改符号的标记,同时需要快速计算修改后的信息。左端点前的符号可以直接得到,而每个区间直接计算的值要么是区间所有数字的和,要么是区间所有数字的积,因此维护这两个信息即可。由于区间修改为 \({+}\) 时靠右的极长乘法段为最右边数的值,也需要记录。综上,多维护每个区间的区间和、区间积、右端点处数字的值即可做到 \({O(n\log n)}\) 计算。
最后加入区间修改值的操作:肯定需要多一个区间修改值的标记,同时需要快速计算修改后的信息。区间和、区间积可以直接计算,左右极长的乘法段需要维护其长度才能计算,这里需要用快速幂,所以有 \({O(\log n)}\) 的复杂度,问题在于如何快速求出修改后区间直接计算的值。考虑可以只关心所有不同长度的极长乘法段的个数,而一个结论是总长度为 \({n}\) 的不同长度最多有 \({O(\sqrt{n})}\) 种,维护这个信息之后可以做到 \({O(\sqrt{n}\log n)}\) 修改,合并时候用归并排序实现,额外考虑两个乘法段产生的新乘法段的贡献,那么上传标记的复杂度为 \({O(\sqrt{n})}\)。由于 \({\sum\limits_{i\ge0}\sqrt{\frac{n}{2^i}}=O(\sqrt{n})}\),因此单次修改的复杂度为 \({O(\sqrt{n}\log n)}\),总复杂度是 \({O(n\sqrt{n}\log n)}\) 的。
考虑优化掉这个 \({\log n }\):其实没有必要每次都重新使用快速幂,只需要保证要计算的 \({\sum k_ix^{\alpha_i}}\) 的 \({\alpha_i}\) 是递增的,\({x^{\alpha_i}}\) 可以从 \({x^{\alpha_{i-1}}}\) 的基础上转移来。可以证明这样子优化后复杂度从 \({O(\sqrt{n}\log n)}\) 降至 \({O(\sqrt{n})}\)。因此总复杂度为 \({O(n\sqrt{n})}\)。
LG11706.「KTSC 2020 R1」穿越
先考虑确定 \({A,B}\) 后如何快速求解答案:
设 \({f(i,j)}\) 表示当前位于 \({(i,j)}\) 所需花费的最小代价。那么每一列都可以从上一列转移过来,如果有墙花费 \({B}\),否则不花费,可以看作对这一列所有有墙的行的 dp 值整体 \({+B}\)。接着可以考虑瞬移的贡献,相当于对于当前列的任意两行 \({x,y}\),\({f(y,j)}\) 需要和 \({f(x,j)+A}\) 取 \({\min}\),直接 dp 可以做到 \({O(n^3)}\)。
可以考虑到瞬移的贡献只有当 \({x}\) 是 \({f(*,j)}\) 的最小值时才有效,因此可以先找到每一列的最小值之后再更新,这样复杂度就是 \({O(n^2)}\) 的。还能发现区间加,全局最小值,全局取最小值都是可以用线段树维护的内容,于是可以做到 \({O(n\log n)}\)。
现在考虑多组询问如何求解:
对于每一组方案可以视作一个点 \({(x_A,x_B)}\) 表示 \({A}\) 花费的次数和 \({B}\) 花费的次数,询问相当于要找到一个点使得 \({Ax_A+Bx_B}\) 最小。不难发现当且仅当这些点在下凸包上时才可能成为答案,于是可以维护所有点形成的下凸包,这样单次询问只需要在凸包上二分即可,复杂度是 \({O(q\log n)}\) 的。
一个结论是定义域和值域均为 \({O(V)}\) 的整点凸包上的点数是 \({O(V^{\frac{2}{3}})}\) 的,现在问题转化为如何快速求出凸包。考虑使用 quick-convex 算法,求带权最小值使用 dp 求解,最多求 \({O(n^{\frac{2}{3}})}\) 次,复杂度为 \({O(n^{\frac{5}{3}}\log n)}\)。综上总复杂度为 \({O((n^{\frac{5}{3}}+q)\log n)}\)。
quick-convex 算法:
考虑 \({L,R}\) 两个点之间的凸包,可以在 \({L,R}\) 中找到一个凸包上的点 \({P}\) 之后继续分治 \({L,P}\) 和 \({P,R}\) 两部分。发现凸包上一定存在一点 \({P}\) 使得 \({\triangle LPR}\) 面积最大,不妨找到这个点。
由于向量叉积的几何意义是向量形成的平行四边形的面积,考虑使 \({\overrightarrow{LR}\times\overrightarrow{LP}}\) 的结果最小(因为此时面积为负),将向量叉积的结果拆开有
\[\begin{aligned} \overrightarrow{LR}\times\overrightarrow{LP}&=(x_R-x_L)(y_P-y_L)-(y_R-y_L)(x_P-x_L)\\&=(y_L-y_R)x_P+(x_R-x_L)y_P-x_Ry_L+y_Rx_L \end{aligned} \]可以看出当 \({(y_L-y_R)x_P+(x_R-x_L)y_P}\) 最小时结果最小,对 \({x,y}\) 分别带 \({y_L-y_R,x_R-x_L}\) 的权求出取到最小值的 \({(x,y)}\) 即可。
LG7881. [Ynoi2006] rmpq
由于运算不具有交换律,因此一些经典的 \({\text{poly}(\log n)}\) 复杂度的数据结构都不可行,考虑如何优化暴力。
当进行了 \({B}\) 次修改时,根据这些修改所依据的分界线,可以将整个平面划分成若干网格,其中每个网格对应的操作序列是相同的,对应的结果也相同,而 \({O(B)}\) 条分界线最多可以分出 \({O(B^2)}\) 个这样的网格,可以在总复杂度 \({O(B^3)}\) 内预处理出所有网格的值。对于每一个询问,最多需要查询 \({\frac{n}{B}}\) 个整块和 \({B}\) 个单独操作,乘法的调用次数相同是 \({O(\frac{n^2}{B}+nB^2)}\) 的,取 \({B=O(n^{\frac{1}{3}})}\) 可以做到 \({O(n^{\frac{5}{3}})}\),时间复杂度由于查询需要在网格上二分,会多一个 \({\log}\),难以通过。
考虑优化 \({O(B^3)}\) 预处理所有块。可以考虑分治,对于前半段操作和后半段操作单独求出网格之后合并两者网格,相当于对应位置相乘,单次可以使用归并排序做到 \({O(B^2)}\),总复杂度是 \({T(n)=2T(\frac{n}{2})+O(n^2)}\),即 \({O(B^2)}\)。这样可以将调用次数优化至 \({O(\frac{n^2}{B}+nB)}\),取 \({B=O(\sqrt{n})}\) 可以做到 \({O(n\sqrt{n})}\),复杂度为 \({O(n\sqrt{n}\log n)}\)。由于常数其实相当小,所以是可以接受的。
实际实现的时候为了方便可以使用二进制分组,每次在最后新添加一个操作网格后暴力向前合并,相当于进行了分治,并且优化了散块的复杂度至 \({O(\log B)}\)。
QOJ1851. Directed Acyclic Graph
可达性问题不是很能用数据结构维护,因此可以考虑操作分块后暴力重构,假设每 \(B\) 次操作分了一块。
对于里面的至多 \(B\) 个询问,可以通过预处理每个点的可达的询问点,之后暴力枚举之前的所有操作更新答案,复杂度可以做到 \(O(\frac{nB}{w}+B^2)\)。
对于暴力重构,发现对于每个点只需要关心最后一次 \(1\) 操作后的 \(2\) 操作。维护最后一次 \(1\) 操作可以利用拓扑序在最后统一下放,对于每个点进行的所有 \(2\) 操作也可以利用拓扑序下放,这样预处理的复杂度是 \(O(n+\frac{nB}{w})\) 的。接下来考虑如何找到最后一次 \(1\) 操作之后的值最小的 \(2\) 操作:可以先对所有 \(2\) 操作按值的大小从小到大排序,之后用 bitset 维护操作时刻 \(\ge t\) 的所有 \(2\) 操作,那么要求的就是两个 bitset 的并。由于已经按权值排过序了,所以只需要知道第一个 \(1\) 的位置即可,单次询问复杂度是 \(O(\frac{B}{w})\) 的。
由于总共有 \(\frac{n}{B}\) 个块,因此复杂度是 \(O(\frac{n}{B}(\frac{nB}{w}+B^2+n))\) 的,取 \(B=O(\sqrt{n})\) 可以做到 \(O(\frac{n^2}{w}+n\sqrt{n})\)。
CF1476G. Minimum Difference
有一个直觉是直接做莫队,由于带修所以使用带修莫队可以做到 \(O(n^{\frac{5}{3}})\) 的复杂度。
现在可以知道每个数字出现的次数,也可以知道所有次数出现的次数。事实上,根据经典结论,最多只有 \(O(\sqrt{n})\) 种不同的次数会出现,将这 \(O(\sqrt{n})\) 种次数提出来之后用双指针找到极大的符合条件的区间 \([l,r]\) 即可,复杂度是 \(O(\sqrt{n})\) 的。于是总复杂度是 \(O(n^{\frac{5}{3}}+n^{\frac{3}{2}})\)。
ABC369G. As far as possible
根据经典结论,只需要对原树做带权长链剖分后选取前 \(k\) 长的链即可,不算排序复杂度 \(O(n)\)。答案是选取链长的二倍,容易理解这是正确的。

浙公网安备 33010602011771号