【UOJ 84】水题走四方(DP)

传送门

题意

给定一棵 \(n\) 个点的有根树。初始有 \(2\) 个人在根结点。每 \(1\) 秒内,每个人要么走向某个儿子,要么停在原地不动。当两人都在某个结点上时,其中一人可以立即传送到另一个人的位置上。最后要求每个结点都被至少经过一次,问总共花费的最小时间。

\(n \le 5\times10^6\)。

分析

先说一个样例,可以卡掉若干个假算法:有两根长筷子粘在根上,且靠近根的一端有一些毛刺。答案应该是比一根筷子的长度多一点,而有些算法会给出接近两根筷子的长度。

考虑问题的子状态:两人位于同一点上。此时子树外的点必然已经被遍历过,我们只要考虑子树内花费的最小时间。为了简化问题,我们用逆向思维,考虑最多能省下多少时间。这样我们默认初始方案的用时为整棵树的叶子结点深度之和。

设 \(f_u\) 为子树答案,\(e_u\) 为子树中的叶子个数。我们枚举下一次集合的地点 \(v\),注意 \(v\) 不一定是 \(u\) 的儿子。那么最优策略一定是:留一个人呆在 \(u\),另一个人把 \(v\) 子树外的结点逛完后传到 \(v\)。注意最后逛到的叶子深度越大越优,因为最后一次逛不用传回 \(u\),而逛的时候之前干等的人就可以开始向 \(v\) 走了。设 \(d_v\) 为 \(v\) 的深度,\(d_m(u, v)\) 为 \(u\) 子树内、\(v\) 子树外的最深叶子的深度。那么可以写出转移式:

\[f_u = \max\{ f_v + (d_v - d_u)e_v - \max(0, d_v - d_m(u, v)) \} \]

直接做是 \(O(n^2)\) 的,突破口在于:大多数转移都是冗余的。先判掉 \(u\) 只有一个儿子 \(v\) 的特例,此时 \(f_{u} = f_v + e_v - 1\)。对其余情况,观察到 \(d_m(u, v) < d_v\) 时肯定不优,因为此时相当于乱逛的人到叶子后还要等另一个人走到 \(v\),那不如刚到叶子就传到 \(v\) 同深度的祖先。这样 \(\max(0, d_v - d_m(u, v))\) 这项就变成了 \(0\)。

更强地,\(v\) 只需转移到满足 \(d_m(u, v) \ge d_v\) 的最深的 \(u\)。这个可以反证得到,假设 \(v\) 还会转移给更浅的祖先 \(w\),那么一定可以让 \(v\) 先转移到 \(u\),再从 \(u\) 通过特判的那种转移来转移到 \(w\)。因为 \(e_u - 1 \ge e_v\),所以这样转移不会使答案变劣。

那么我们只要找到每个 \(v\) 对应的 \(u\) 即可。接下来的做法就五花八门了,我写的是长链剖分。从下往上考虑一条长链,维护一个栈,每次把当前的 \(v\) 加入栈顶。当出现短儿子时,我们只要弹掉深度不超过某个数的 \(v\) 即可。最终栈里还剩下一些点,那么它们一定是转移给链顶的父亲。

因为要先算短儿子再算长儿子,我一开始直接写了递归,然后就 MLE 了。因此要用计数排序预处理出 dfn,再非递归地算,这样才能通过(口区)。

递归版

非递归版

上一篇:测试平台系列(84) 支持复制其他前置条件


下一篇:寒假笔记本9:碱