Solution Set -「DS 专题」兔年的兔子写 DS 会有小常数吗?
大家好, 她是小梅香, 是雨兔最可爱的妹妹. (
仅有对 DS 的非平凡使用会打 tag, 如果仅仅拿线段树维护个区间加减区间和啥的就不打了.
Day 1
「Ynoi 2009」「洛谷 P6109」rprmq1 ^
- Link & Submission.
「Ynoi Easy Round 2021」「洛谷 P8512」TEST_152
-
Link & Submission.
-
「C.复杂度分析」
操作只有区间赋值? 立马向珂朵莉树的方向思考. 注意若模拟所有 \(n\) 次操作, 一共只有 \(\mathcal O(n)\) 次区间贡献变化, 而这些变化也仅仅是单纯的区间加减. 因此, 可以对询问的 \(r\) 扫描线, 用数据结构维护每个 \(l\) 对应的答案. 复杂度 \(\mathcal O((n+q)\log m)\).
「Ynoi 2005」「洛谷 P7907」rmscne
-
Link & Submission.
-
「C.性质/结论」
这题麻烦的地方在于, 最终的答案区间很可能有 \(l<l'\le r'<r\), 左右端点都会向内缩, 我们就没有办法固定一端做扫描线之类的工作. 这也提示我们, 应该先去思考关于合法区间的性质.
其实, "枚举端点" 是 "固定端点" 的有力方法. 对于询问 \([l,r]\), 设有最小的 \(p\), 使得 \([p,r]\) 合法. 我们在 \([l,p]\) 中枚举左端点 \(l'\), 找到此时使得 \([l',r']\) 关于 \([l',r]\) 合法的最大 \(r'\), 再求 \(r'-l'+1\) 的最大值就是答案.
通过等价转化, 我们使 \(r'\) 仅与左端点 \(l'\) 和固定端点 \(r\) 有关, 这时候就能对 \(r\) 扫描线了. 令 \(p_i\) 表示 \(l'=i\) 时最小的 \(r'\), 对于询问, 我们需要完成两个工作: 1) 求出 \(p\); 2) 求出 \([l,p]\) 中 \(p_i-i+1\) 的最大值. 对于前者, 维护方法很多, 但用 DSU 之类的小常数做法才不会被卡常. 对于后者, 可以用线段树简单维护. 于是我们得到了 \(\mathcal O((n+q)\log n)\) 的做法.
「Ynoi 2010」「洛谷 P6105」y-fast trie
-
Link & Submission.
-
「B.Tricks」「C.性质/结论」
对 \(i+j\ge C\) 的情况, 维护全局最大次大即可. 我们主要讨论 \(i+j<C\) 的情况. 设在某一时刻 \(i\) 的最优匹配数为 \(p(i)\), 显然仅有 \(p(p(i))=i\) 的 \(i+p(i)\) 可能贡献答案 (trick), 我们需要时刻维护这一双向最优匹配关系.
这里以插入的讨论为例. 设当前集合还未插入, 即将插入 \(x\). 找到 \(y=p(x),z=p(y),w=p(z)\). 若 \(y\) 存在且(\(z\) 不存在或者 \(z<x\)), 则 \((x,y)\) 为新增双向对, 在此条件下此, 若 \(z\) 存在且 \(w=y\), 则 \((y,z)\) 为被 \((x,y)\) 顶替, 将被删除的双向对. 用可删堆维护双向对贡献, std::map
或者 std::multiset
维护集合以计算 \(p\) (推荐前者, 因为 std::multiset::count
的复杂度不正确), 可以做到 \(\mathcal O(n\log n)\).
「北大集训 2021」「LOJ #3673」简单数据结构
-
Link & Submission.
-
「A.分治-整体二分」「A.分治-数据结构上二分」「A.数据结构-李超树」「A.数据结构-线段树」「C.细节」
(注意代码 L112 amx[u] += k * rig[u]
逻辑有误, rig[u]
应改为区间内 activated 的最右位置而非区间右端点. 应该能卡, 不知道为什么能过.)
自然地发现, 若所有 \(a_i\) 初始为 \(0\), 则 \(\{a_n\}\) 永远单调, 操作 1 的效果变为后缀赋值, 这时结合线段树二分, 我们可以用线段树方便地维护操作.
受此启发, 我们尝试把原问题化归到这种情况. 此时, 我们需要求出每个 \(a_i\) 第一次被取 \(\min\) 的时间 \(t_i\): \(t_i\) 之前, \(a_i\) 不会被取 \(\min\), 维护很方便; \(t_i\) 之后, 所有同 \(a_i\) 一样被取过 \(\min\) 的 \(a_j\) 构成的子序列是单调的, 可以用上面的方法维护. 这部分可以做到 \(\mathcal O(q\log n)\), 只是实现较为恶心.
最后的问题, 如何求 \(t_i\)? 注意每个操作 1 前操作 2 的次数一定, 因此对于 \(a_i\) 若其一直未被取 \(\min\), 到第 \(j\) 个操作 1 时的值可以写作 \(k_j\times i+a_i\). 整体二分, 李超树插入这些直线以划分左右子问题即可. 这部分可以做到 \(\mathcal O(n\log^2n)\).
Day 2
「北大集训 2021」「LOJ #3676」小明的树
-
Link & Submission.
-
「A.数据结构-线段树」「B.Tricks」
经典老番:
- 美丽 \(\Leftrightarrow\) 黑点构成连通块 \(\Leftrightarrow\) 第 \(i\) 次操作后, \(n-1-i=c\), \(c\) 为黑-黑边数.
- 美丽时, 白点连通块数 \(=d\), \(d\) 为黑-白边数.
- \(n-1-i\ge c\).
线段树在时间轴上维护区间最小值及其对应黑-白贡献即可. \(\mathcal O((n+m)\log n)\).
「BalkanOI 2018」「洛谷 P4786」Election ^
- Link & Submission.
「HNOI 2011」「洛谷 P3215」括号修复
-
Link & Submission.
-
「A.数据结构-平衡树」「C.性质/结论」「C.细节」
对括号串的处理应该是得心应手了. 设 \(\chr (,\chr )\) 分别对应 \(1,-1\), 前缀和最小值为 \(p\le 0\), 后缀和最大值为 \(q\ge 0\), 不难证明答案为 \(\lceil-p/2\rceil+\lceil q/2\rceil\). 用一大坨平衡树维护这两个值即可. 复杂度 \(\mathcal O((n+q)\log n)\).
「HNOI 2015」「洛谷 P3242」接水果
-
Link & Submission.
-
「A.分治-整体二分」「B.DFN」
整体二分答案, 盘子对水果的贡献形式是 DFN 上的二维数点. \(\mathcal O((n+q)\log^2n)\).
「Ynoi 2010」「洛谷 P6018」Fusion tree
-
Link & Submission.
-
「A.数据结构-Trie」
波奇酱剖分 定根, 每个点维护孩子们的点权集合. 难点在于对集合的整体 \(+1\).
分析发现, \(+1\) 进位的数一定是奇数, 即最低 bit 为 \(1\), 进位等价于对从次低 bit 开始的数 \(+1\), 这是一个子问题. 以 Trie 树的视角, 对 \(u\) 子树 \(+1\) 相当于交换左右子树, 然后递归向左子树. 这样就能维护了. 最终复杂度 \(\mathcal O((n+q)\log V)\).
「OurOJ #6861」party
简要题意
给定一棵含 $n$ 个点的有根树, 点有颜色. 处理 $q$ 次询问, 每次给出 $c$ 个点, 每个点在自己走向 LCA 的路径上选择相同数量的若干颜色, 颜色不能与其他点选的颜色有交. 回答最多选出的颜色总数.$n\le3\times10^5$, 颜色种类 $m\le10^3$, $q\le5\times10^4$, $c\le5$.
- 「B.复杂度平衡」「C.性质/结论」
我似乎写了个怪做法.
"选出颜色数量相同" "颜色不交", 若已知每个点选 \(k\) 个颜色, 则将每个点复制 \(k\) 份, 每个复制点连向原点能选择的颜色, 用 Hall 定理检查合法性. 反过来, 枚举 Hall 定理检查的集合 \(S\), 设 \(S\) 中的点总共能选择 \(c_S\) 种颜色, 则有限制 \(k\le c_S/|S|\). 只需要求出所有 \(c_S\), 取最小值即为答案.
注意 \(m\times q\times c\) 勉强能被接受, 可以直接枚举颜色, 用 \(\mathcal O(\sqrt n)\) 区间加 \(\mathcal O(1)\) 查单点的序列维护手法维护每个点祖先内的当前颜色数量, 再枚举询问和询问中的结点, 求出可以选择当前颜色的结点集合 \(S\) 暂存入 \(c_S\). 此后回答询问时, 先用 FMT 之类的东西还原出 \(c_S\), 再求答案即可.
复杂度有三部分, 大概是 \(\mathcal O(n\sqrt n+mqc+qc2^c)\), 不过规避了 std::bitset
. (
Day 3
「Ynoi Easy Round 2020」「洛谷 P8264」TEST_100
- Link & Submission.
- 「A.分块」「A.数据结构-并查集」「B.Tricks」
先给这个 "线性变换" 一个比较形象的解释: 让 \(x\) 向 \(0\) 位置走 \(a_i\) 步. 因此可以发现,一个区间内的数分别经过同样的若干变换后的结果仍然构成一段区间.
利用这个结论, 我们可以考虑分块. 对于整块 \([l,r]\), 维护映射关系 \(\varphi_{[l,r]}:[1,n]\to[0,n]\), 用以描述 \([1,n]\) 从左侧输入 \([l,r]\) 后, 在 \(r\) 输出的结果. 变换过程是对区间的位移和折叠, 这是一个经典 trick, 用并查集维护, 由于必要的并查集找根操作是在并查集定形后统一进行的, 所以并查集的 \(\alpha(n)\) 也是可以规避的. 最终复杂度 \(\mathcal O((n+m)\sqrt n)\).
「POI 2015」「洛谷 P3591」ODW
- Link & Submission.
- 「B.复杂度平衡」
维护个 \(\mathcal O(1)\) \(k\) 级祖先, 步长 \(\mathcal O(\sqrt n)\) 以内的维护祖先上与当前点深度关于步长同余的结点权值和 \(\mathcal O(1)\) 查询; 步长 \(\mathcal O(\sqrt n)\) 以上的暴力跳. 复杂度 \(\mathcal O(n\sqrt n)\).
「Ynoi 2014」「洛谷 P5063」置身天上之森
- Link & Submission.
- 「A.分块」「C.性质/结论」
注意到线段树区间长度仅有 \(\mathcal O(\log n)\) 种, 当区间长度想同时, 批量修改才容易完成, 因此我们可以直接对这 \(\mathcal O(\log n)\) 种长度的区间分别求解.
对于固定的区间长度, 我们需要处理区间加, 区间求 rank. 分块后维护块内排名即可.
平衡块长得到理论最优复杂度 \(\mathcal O(m\sqrt{n\log n})\), 注意不要在不必要的地方引入瓶颈 (比如, 一些地方可以归并排序).
「Ynoi 2018」「洛谷 P5397」天降之物
-
Link & Submission.
-
「B.复杂度平衡」「B.Tricks」
维护颜色, 很自然地想到根号平衡. 这里算是有个没见过的 trick: 设阈值为 \(B\), 将集合 \(S\) 表示为 \(P\cup Q\), 其中 \(|Q|<B\). 小集合之间与大集合之间的合并都可以暴力, 小集合合并入大集合时, 先加入 \(Q\), 若 \(|Q|\ge B\) 再重构 \(S\). 计算贡献时, \(Q\) 仍然可以作为一个小集合算答案, 这样我们的复杂度得以保证. 复杂度 \(\mathcal O(m\sqrt n)\), 实现时只需要记录 \(Q\) 和大集合与其他集合间的答案.
「Ynoi 2018」「洛谷 P4119」未来日记
- Link & Submission.
- 「A.分块」「B.复杂度平衡」「C.细节」
向身边的 Ynoi 大师询问得知这题卡 \(\log\), 那么第一个思考的点就是如何再不引入 \(\log\) 的情况下求第 \(k\) 小? — 像主席树的静态第 \(k\) 小一样, 若我们得到了左右端点的前缀和序列, 且知道每 \(\sqrt n\) 个数的和, 我们就可以 \(\mathcal O(\sqrt n)\) 地求出第 \(k\) 小.
问题转化到维护这一前缀和序列. 显然不可能对每个端点维护, 我们还需要分块, 仅维护整块右端点处的前缀和序列. 整块用并查集维护颜色转换 (比较复杂, 注意空间消耗), 散块暴力, 可以做到 \(\mathcal O((n+m)\sqrt n)\).
卡了 \(17\) 发常, 注意规避瓶颈处的 random acess. 卡常的时候可以先注释代码找到实际瓶颈再针对性卡常.
Day 4
「Ynoi 2017」「洛谷 P5356」由乃打扑克
- Link & Submission.
- 「A.分块」
分块, 修改时整块标记散块归并, 查询二分答案, 再枚举每个块二分数量…
啊你说这个东西是人人都会的暴力怎么可能有分? 设块长为 \(B\), 复杂度 \(\mathcal O(B+n/B+\log V\cdot n/B\cdot\log n)\), 取 \(B=\sqrt{n\log n\log V}\), 平衡出 \(\mathcal O(n\sqrt{n\log n\log V})\), 也就是 \(n\sqrt n\) 带一个 \(\log\), 可以过.
注意二分时 \(l+r\) 可能超过 \(2^{31}\), 会被 hack.
「洛谷 P2137」Gty 的妹子树
- Link & Submission.
- 「A.分块」
操作分块, 对于原有结点, DFN 序列上值域线段树处理查询; 对于新增结点, 通过维护最接近的原有结点的 DFN 来判断子树关系并处理查询. 理论最优复杂度 \(\mathcal O(m\sqrt{n\log n})\).
「洛谷 P3863」序列
- Link & Submission.
- 「A.分块」
在原序列上扫描线, 分块维护时间轴, 把由乃打扑克的代码贺过来改改就完了. 理论最优复杂度 \(\mathcal O(q\sqrt{q\log q})\).
「Ynoi 2018」「洛谷 P4117」五彩斑斓的世界 ^
- Link & Submission.
「Ynoi 2019」「洛谷 P6578」魔法少女网站
- Link & Submission.
- 「A.分块」「B.离线」
我们几乎无法避免 \(\mathcal O(1)\) 处理块内 lower-bound, 但空间不允许…
一个简单的处理方法是按块离线, 每次处理一块区间的修改和询问. 再用一个分块动态维护 \([1,n]\) 内的数在当前块的排名, 同时处理块内答案, 为了处理块间贡献, 还要处理类似第一次最后一次出现位置的信息. 维护一大堆东西, \(\mathcal O(m\sqrt n)\).