题解:[SCOI2008] 城堡

· · 题解

逆天的阅读体验

题目链接

应该是联赛前最后一次任性了,浪费的时间有点多,不过也揭露了我的基础知识和代码能力都很弱的问题,得加油啊。

先 sto dwt。

给定一棵基环树森林,起初有 m 个点已被选进 S 里,你需要再选 k 个点加入到 S 中,最小化其余点到 S 距离的最大值。

这个问题直接做非常困难,考虑二分答案转成判定性问题:选定若干个点能够使得所有点到 S 的距离不超过目前二分的答案 F。如果一个节点距离 S 中的点不超过 F,称其为被覆盖了

基环树的题自然是树和环分开考虑,依然要预处理记好在环上的点和环上点之间的距离。如果放在树上,原题是这个,在基环树的树部分依然采用相同的 DP 方式,这个 DP 也是基于贪心的:能不放就不放。设 f_{u} 表示以 u 为根的子树中离 u 最远的没被覆盖的节点到 u 的距离;g_{u}u 为根的子树中在 S 中的离 u 最近的节点到 u 的距离,那么初值为 f_{u} = 0,g_{v} = \infty,转移方程为 f_{u} = \max_{v \in son_{u}} \{ f_{v} \} + 1,g_{u} = \min_{v \in son_{u}} \{ g_{v} \} + 1。接下来考虑一些边界情况:

对于环上每个点为根做一遍这个树形 DP,可以得到树中最少需要选多少个点,以及根据根节点 if_{i} 值和环上边长来知道必须要在环上的某一段区间内选点进入 S 才能使得 i 子树内的和 i 距离最大的未被覆盖的节点合法。特别的,如果环上存在一个节点 j,使得 f_{i} + g_{j} + dis_{i \to j} \leq F,即用 j 子树中的离 j 最近的 S 中的点来覆盖 i 中的点,那么对于 i 就不会存在环上的这段选择区间。这一部分的形态形如寻找前后缀 a \pm b 的最小值,可以用前缀 \min 线性解决。找环上的需要被覆盖的区间可以先算出最深的不合法节点到根节点的距离,然后推出在环上的延伸距离,由于我们预先处理好了环上边权的前缀和,就可以在前缀和上做一个二分查找来找到区间的左右端点。

解决上述步骤之后,现在问题转化成了在环上有若干个区间,每个区间至少选一个点,问最少要选多少个点。由于起初的 m 个点有一些可能在环上,先把这些点能满足的区间去掉。然后可以运用这个题的套路,加入当前选择了一个位置 i,能够发现下一个选择的节点一定是左端点大于这个节点,且右端点最靠左的那个区间的右端点,可以用单指针线性求出从位置 i 开始下一个选择的位置在哪里,然后预处理倍增数组 st_{i,j} 表示选择了位置 i,下 2^j 个位置选在哪里,枚举哪个节点作为第一个选择的节点,倍增的算接下来总共在环上要选几个点,于是这样就 \mathcal O(n \log n) 的解决这个部分了。

让我们梳理一下整个算法流程:首先预处理环上节点,断环成链并记录环上边权前缀和;然后开始二分答案,对于一棵基环树,先树形 DP 算出树部分最少需要放置的点以及能够覆盖到的环上的区间,然后倍增搭配贪心完成环上的选取;用所有基环树需要选取的点的数量总和来判断是否满足当前二分出的答案。因为要特殊处理二元环和自环的环上边权,我的代码中选择了使用 map 来解决这个问题。

综上,总复杂度我们做到了 \mathcal O(n \log n \log V)。但是这个东西实际跑起来非常缓慢,瓶颈主要在于倍增数组的初始化,更改倍增数组的类型可以起到很好的优化效果,同时这也说明了卡满这个做法的方法是一整个圈。还有一些卡常技巧也可以相应的使用,我的代码常数并不优秀,但 10^5 的数据还是跑到了一秒左右。代码在这里。