【2025.12.13-2025.12.25】北京多校集训 —— 正常复杂度数据结构

【2025.12.13-2025.12.25】北京多校集训 —— 正常复杂度数据结构

CF522D. Closest Equals

考虑一定是相邻的相同数字会贡献给答案,对每个位置 \({i}\) 求一个 \({\text{pre}_i}\) 表示前面第一个值和它相等的位置,那么 \({(\text{pre}_i,i)}\) 会给所有 \({l\le \text{pre}_i\land i\le r}\) 的区间 \({(l,r)}\)\({i-\text{pre}_i}\) 的贡献。对 \({r}\) 扫描线之后单点查询,每次要做的就是一个前缀取 \({\min}\) 操作,可以用树状数组维护做到 \({O(n\log n)}\)

LG9991. [Ynoi Easy Round 2023] TEST_107

和上一题类似,一个 \({(\text{pre}_i,i)}\) 对不同的询问区间有不同的贡献:

  • \({\text{pre}_i<l\le i\le r}\),对区间 \({(l,r)}\)\({i-l}\) 的贡献。
  • \({l\le \text{pre}_i<i\le r}\),对区间 \({(l,r)}\)\({i-\text{pre}_i-1}\) 的贡献。
  • \({l\le \text{pre}_i\le r<i}\),对区间 \({(l,r)}\)\({r-\text{pre}_i}\)​的贡献。

对于第一个贡献,对 \({l}\) 扫描线后,当 \({\text{pre}_i<l}\) 时所有 \({\ge i}\)\({r}\) 都会新增 \({i-l}\) 的贡献,因为 \({l}\) 对于查询是定值,因此只需要维护 \({i}\),用树状数组实现后缀取 \({\max}\),单点查询。第三个贡献的计算是类似的。

对于第二个贡献,和上一题类似的,对 \({r}\) 扫描线后前缀取 \({\min}\),单点查询即可。但为了方便,也可以对 \({l}\) 扫描线后后缀取 \({\max}\)。同样可以用树状数组实现。

显然总复杂度是 \({O(n\log n)}\) 的。

CF407E. k-d-sequence

先特判掉 \({d=0}\) 的 corner case,显然区间中的数一定全部相等,使用双指针即可做到 \({O(n)}\)

对于其他的情况,可以发现为了满足条件,区间内的点\({\bmod d}\) 一定相同,可以先找出这些极长段之后单独求解。之后将所有数从 \({a_i}\) 修改为 \({\lfloor\frac{a_i}{d}\rfloor}\),这样就将等差 \({d}\) 变为了等差 \({1}\) 的问题。满足条件的 \({a_i}\) 序列应当满足几个条件:

  • 没有相等的 \({a_i}\)
  • \({(\max(a_i)-\min(a_i)+1)-(R-L+1)\le k}\),化简之后可以得到 \({\max(a_i)-\min(a_i)+L\le k+R}\)

第一个条件相当于当 \({R\ge i}\)\({L> p_i}\),其中 \({p_i}\)\({a_i}\) 上一个出现的问题,因此当前的右端点 \({R}\) 合法的最远左端点应当是所有 \({p_i}\)\({\max}+1\)。对于第二个条件可以做扫描线,对每个 \({L}\) 维护 \({\max-\min+L}\),线段树二分找到最小的满足条件的位置即可。现在如何考虑维护线段树,一个经典的技巧是用单调栈维护线段树上取到某个 \({\max}\)\({\min}\) 的区间,在单调栈弹栈或入栈时相应的在线段树上更新,复杂度可以做到 \({O(n\log n)}\)

LG11398. 众数

对于众数没有特别好的处理办法,结合 \({\sum k\le 5\times 10^7}\) 的性质可以想象到如果可以快速维护出每个位置的众数即可做到 \({O(\sum k)}\) 回答询问。

最基础的办法是每次修改都 \({O(n)}\) 更新众数,可以做到 \({O(n^2)}\),但不够优秀。也可以使用线段树用于单点修改,全局查询 \({\max}\),这样只需要在全局信息的基础上单点减就可以单次 \({O(\log n)}\) 算出这个点的众数,这样可以做到 \({O((n+\sum k)\log n)}\) 的复杂度。这些方法都可以特殊化适用于 \({1\sim 15}\) 号点。

对于 \({16\sim 19}\) 号点,因为 \({k\le 300}\),所以前 \({n-300}\) 个点的信息一定是需要的,并且每次查询只关心后 \({300}\) 个点的信息,因此可以直接将前 \({n-300}\) 个点的信息汇总到一起,查询的时候动态加入后面点的信息。因为只需要维护一个前缀的信息,所以更新只需要 \({O(1)}\)。由此复杂度可以做到 \({O(n+300m+\sum k)}\)

从上一个做法得到启发,如果可以适当的选取关键点,那么更新只需要维护这些关键点前缀的信息,查询则可以通过从某一个关键点递推来。问题在于如何适当的选取关键点使得这些关键点的个数不多,且对于所有 \({k}\) 都能找到一个关键点和 \({n}\) 的距离的级别是 \({O(k)}\) 的。可以想到倍增,取所有和 \({n}\) 距离为 \({2^k}\) 的点记为关键点,这样更新的复杂度是 \({O(\log n)}\),而对于每个 \({k}\),都存在一个关键点和 \({n}\) 的距离 \({\le 2k}\),这样查询的总复杂度还是 \({O(\sum k)}\) 的。具体实现可以考虑从后向前枚举所有关键点,从这个关键点出发递推出后面所有点的权值后暴力比对即可。总复杂度为 \({O(n\log n+\sum k)}\)

CF1814F. Communication Towers

一个朴素的想法是对于每一个频率 \({x}\),计算出点 \({1}\) 可以到达哪些点,所有 \({x}\) 的答案并起来就是最终的结果。

对于每一个点 \({i}\),都只有 \({x\in [l_i,r_i]}\) 时才能经过这个点,这相当于一条边 \({(u,v)}\) 只有当 \({x\in [l_u,r_u]\cap[l_v,r_v]}\) 的时候才能经过。容易想到利用线段树分治+可撤销并查集维护图的连通性,可以做到 \({O(n\log ^2n)}\)

问题在于如何找出所有和点 \({1}\) 连通的点。直接做会退化成 \({O(n^2)}\)。可以考虑在可撤销并查集上打标记,在线段树分治的所有叶子节点处,将点 \({1}\) 所在并查集的根打上 \({+1}\) 标记,在撤销的时候下放到另一个并查集的根上。需要注意合并 \({u,v}\) 的时候 \({v}\) 需要提前减去 \({u}\) 的标记,这是容易理解的。

LG11111. [ROI 2023] 生产计划 (Day 2)

先将所有点取到最小值 \({l_u}\) 后求出一组最大独立集 \({L}\),接着考虑将所有点逐渐调整至 \({r_u}\)。假设按照某一个顺序 \({p_i}\),依次将 \({p_1,p_2,\cdots,p_n}\) 的值从 \({l_u}\) 单次 \({+1}\) 调整至 \({r_u}\)。显然每一次调整最大独立集的大小要么不变,要么 \({+1}\),并且最终会变成所有点取到最大值 \({r_u}\) 时的最大独立集 \({R}\)。因此,在 \({[L,R]}\) 之内的所有效率全部可以给出一组构造,否则无解。

现在考虑如何给出一组解,设 \({V_i}\)\({1\sim i}\)\({r_u}\)\({i+1\sim n}\)\({l_u}\) 的最大独立集,显然 \({V_i}\) 单调不减。同时考察 \(i\)\({l_u}\) 变到 \({r_u}\) 的过程,显然当 \({i}\) 的值大于某个值后,最大独立集一定增大,即对于每个点会使得最大独立集增大的值是一个后缀。因此对于一个效率 \({v}\),只需要找到第一个不小于 \({v}\)\({V_i}\),那么 \({1\sim i-1}\) 全部取 \({r_u}\)\({i+1\sim n}\) 全部取 \({l_u}\)\({i}\)\({r_u-(V_i-v)}\) 一定是一组合法的解。

可以预处理哈希的前缀积和后缀积,回答哈希可以做到 \({O(1)}\),给出方案是 \({O(n)}\),加上二分的 \({O(\log n)}\),总复杂度可以做到 \({O(q\log n+\sum n\times c)}\)。取 \({p=(1,2,\cdots,n)}\) 需要使用全局平衡二叉树优化 ddp 可以做到 \({O(n\log n)}\) 预处理,但是取 \({p}\) 为 dfn 序可以直接用换根 dp 做到 \({O(n)}\)

CF1344E. Train Tracks

如果一辆火车成功抵达终点,它将会将路径上所有点的方向改到路径上。将对应方向的边看做实边,其余的边看做虚边,可以发现这个过程和 LCT 的 access 操作很像。而路径上所有方向不在路径上的点一定需要在上一辆火车经过后到这一辆火车经过前操作,不妨直接用 LCT 维护每个点上一次被火车经过的时刻。那么在 access 操作时向上跳的每一条虚边都对应了一个形如 \({[L,R]}\) 时刻内至少要操作一次该点的限制,结合 LCT 的时间复杂度可以理解这样的限制最多有 \({O(n\log n)}\) 个。用其他方法可以严格证明这样的限制不超过 \({2n\log n}\) 个。

对于这 \({O(n\log n)}\) 个限制,可以贪心判断其是否存在可行解:先对 \({L}\) 从小到大排序,依次加入等待队列中,每一个时刻选出等待队列中 \({R}\) 最小的限制,并判断能否满足它,可以证明这样贪心是正确的。如果没有爆炸,操作的次数就是限制的个数;否则记爆炸时间为 \({t}\),因为 \({[L,R]}\) 内没有操作时会在 \({R}\) 时爆炸,因此只需要操作所有 \({R<t}\) 的限制。

\(k=n\log n\),则总复杂度可以做到 \(O(n\log n+k\log k)\)

LG10789. [NOI2024] 登山

先将每个点的冲刺范围转为其冲刺到的点的深度范围,即 \({L_i=d_i-r_i,R_i=d_i-l_i}\)。同理将高度差限制转化为深度限制,即 \({H_i=d_i-h_i-1}\)。记 \({\text{sub}(u)}\) 表示 \({u}\) 的子树,\({\text{anc}(u)}\) 表示 \({u}\) 的祖先。

每一次冲刺的点 \({u}\) 需要满足之前点给出的高度限制 \({\min H}\),因此有 \({d_u\le \min H}\)。由于 \({H_u<d_u}\),因此之后的点的限制和 \({u}\) 之前经过的点无关,可以基于这个性质 dp:设 \({f_u}\) 表示从 \({u}\) 出发到根节点的方案数。\({u}\) 到下一次冲刺的 \({v}\) 应当是先下滑到子树内的某个点 \({k}\) 之后从 \({k}\) 冲刺到 \({v}\),设 \({u}\)\({k}\) 路径上的 \({H}\) 的最小值为 \({H_{u\to k}}\),那么应该有 \({L_k\le d_v\le \min(R_k,H_{u\to k})}\)。于是有转移方程

\[f_u=\sum_{k\in\text{sub}(u)}\sum_{v\in\text{anc}(u)}[L_k\le d_v\le \min(R_k,H_{u\to k})]f_v \]

直接做是 \({O(n^3)}\) 的,在树上做前缀和(即求出所有根链的和)可以优化到 \({O(n^2)}\)

从填表的方式开看很难继续优化,不妨改成刷表。事实上,将每个 \({k}\)\({u}\) 的贡献可以拆成 \({s_{k,\min(R_k,H_{u\to k})}-s_{k,L_k-1}}\) 的形式(前提是 \({L_k\le H_{u\to k}}\),否则不存在合法的 \({v}\)),其中 \({s_{k,d}}\)\({k}\) 的深度为 \({d}\) 的父亲的根链的 \({f}\) 和。可以考虑在算出某个 \({s_{k,d}}\) 之后将其贡献给对应的 \({u}\)

对于 \({s_{k,L_k-1}}\),会被贡献至所有满足 \({H_{u\to k}\ge L_k}\)\({u}\)。由于 \({H_{u\to k}}\) 具有单调性,可以利用倍增快速找到 \({k}\) 的祖先中第一个不满足条件的点,之后是一个链加。

对于 \({s_{k,\min(R_k,H_{u\to k})}}\),分类讨论 \({R_k}\)\({H_{u\to k}}\) 的大小关系:

  • \({R_k\le H_{u\to k}}\),此时 \({s_{k,R_k}}\) 会被贡献至所有满足 \({H_{u\to k}\ge R_k}\)\({u}\),同理可以利用倍增找到范围之后链加。

  • \({H_{u\to k}<R_k}\),因为 \({H_{u\to k}}\) 本质上是一个后缀 \({\min}\),因此只会在严格后缀最小值处改变,因此对于 \({k}\) 的根链,也可以将其拆分成若干链加,但若直接处理复杂度将退化至 \({O(n^2)}\)。可以将贡献从 \({k}\) 转到某个后缀最小值 \({x}\) 处统计,当 \({x}\) 作为后缀最小值时,受其影响的链(即 \({H_{u\to x}=H_x}\) 的所有 \({u}\))是固定的,此时只需要知道有多少个 \({k}\) 能够让作为后缀最小值。

    对于每一个 \({k}\),会让其深度范围在 \({[L_k,R_k-1]}\) 的祖先收到贡献,将其全部汇总到后缀最小值处。不妨让 \({x}\) 指向根链上第一个 \({H}\) 比自己小的点,容易发现这个指向关系构成森林。于是后缀最小值的贡献加,本质上是对森林上的某条链进行链加,可以用树上差分统计,得出结果之后在原树上链加更新答案即可。

于是将问题转化成了 \({O(n)}\) 次链加和单点查询问题,可以用树状数组做到 \({O(n\log n)}\)

posted @ 2026-01-30 16:03  DycIsMyName  阅读(3)  评论(0)    收藏  举报