luogu 3629

原题链接

题意

给定1棵\(n\)个点的树,各边边权均为1。你要从1号点出发,经过所有的点最后返回1号点。

现在你要在这棵树上新增恰好\(k\)条边,且必须保证你的行走路线经过这\(k\)条边恰好1次。

请你为这\(k\)条边指定端点,使得行走路径长度最小化。\(3 \leq n \leq 10^5 , 1 \leq k \leq 2\)

题解

\(k = 0\)时路径长度只能为\(2n - 2\)

\(k = 1\)时,若新边为\((x,y)\),则意义在于从1到达\(x\)之后,不用返回1,直接到达\(y\)\(y\)直接到1。因此节省的路径长度为\(dis(x,y) - 1\)。故求1次树的直径即可。

\(k = 2\)时,若第2条新边为\((a,b)\),假若\(x \rightarrow y\)\(a \rightarrow b\)有重叠部分,则重叠部分实际没有起到减少路径长度的作用。假设重叠的长度为\(same(x,y,a,b)\),则行走路径总长为\(2n - 2 - (dis(x,y) -1) - (dis(a,b) - 1) + 2 \times same(x,y,a,b) = 2n - dis(x,y) - dis(a,b) + 2 \times same(x,y,a,b)\)

这样1来还怎么搞,\(n^4\)枚举\((x,y,a,b)\)

怎么搞?想办法搞!

解法一

老师,我会大胆猜想!

结论:在\(k = 1\)的基础上做。即:选择的两条路径中,至少有1条是原树的直径。

证明:两条路径的实际贡献,实际为它们不相交部分的总边数,即2个“叉”的总长度。根据直径的最长性,假设我们强制令这两个叉的2个端点为直径的两端点,则答案一定更优。

由此,确定了第1条路径的选取后,第2条路径的选取,只需最大化\(dis(a,b) - 2 \times same(x,y,a,b)\)

看起来选择\((a,b)\)还要考虑其与\((x,y)\)的公共路径长度,很不好处理。但本质上这个问题仍旧是树上最优化路径问题,考虑将其转化成求树直径这一能够\(O(n)\)求解的经典问提。具体地,我们可以将所谓公共部分的影响,转化成静态的贡献,即将\((x,y)\)上的边权置为\(-1\),即可得解。

顺带一提,当树上存在负权边时,不能够通过两次BFS/DFS求直径。原因是两次BFS/DFS求直径的正确性基于贪心思想,即类似a > b则a + c > b的式子,与dijkstra是一个道理。通用的求解直径的方法是树形Dp。这一点也启发我们,学算法要学到本质上,不能似是而非。

时间复杂度\(O(n)\)。[代码见此](https://github.com/littlewyy/OI/blob/master/luogu 3629 greedy.cpp)

解法二

老师,我会转换思路,看透本质!

上文的做法是基于先考虑各自的贡献,再减去重复部分的贡献

还有1种不错的思路是,只考虑非重复部分的贡献

具体地,对于两条相交路径,我们只对不相交部分计数。可以发现不相交部分一定可以组成2条不相交路径。因此问题转化为,在树上选择\(2\)条不相交路径,最大化\(2\)条路径总长。

点击查看初始思路 考虑树形Dp,对于根节点x,我们对各棵子树依次考虑。对于当前子树y,有以下几种情况: 1. x与y相连,且y的子树内,除了y直下的路径,还存在1条与其不相交的路径 2. x与y相连,且y的子树内仅有1条从y直下的路径 3. x与y不相连,且y连接情况任意,y的子树内有任意条不相交路径 据此,设计状态f(i)(j)(s)表示以i为根的子树中,共有j条不相交路径,且i的状态为s的最大路径长度和。s = 0表示i没有连边,s = 1表示i从1个儿子直下,s = 2表示i从2个儿子直下。 状态转移方程: 1. s = 0,枚举j和lj,令f(i)(lj)(0)与f(y)(j - lj)(0/1/2)进行组合 2. s = 1,i从y直下 (1) j = 1 f(i)(1)(1) = max(f(i)(1)(1),f(y)(1)(1) + 1) (2)j = 2 1条路径来自之前的子树,f(i)(2)(1) = max(f(i)(2)(1),f(i)(1)(0) + f(y)(1)(1) + 1) 2条路径均来自该子树,f(i)(2)(1) = max(f(i)(2)(1),f(y)(2)(1) + 1) 3. s = 2,i的其中1条从之前儿子直下,1条从y直下 (1)j = 1 f(i)(1)(2) = max(f(i)(1)(2),f(i)(1)(1) + f(y)(1)(1) + 1) (2)j = 2 前2条后1条,f(i)(2)(2) = max(f(i)(2)(2),f(i)(2)(1) + f(y)(1)(1) + 1) 前1条后2条,f(i)(2)(2) = max(f(i)(2)(2),f(i)(1)(1) + f(y)(2)(1) + 1) 值得注意的是,s较大的情况,有赖于s较小且不包含该子树的dp值,因而应该倒序循环

全错辣!不禁骂自己是个sb,混淆了边不能相交与点不能相交的概念。点不能相交意味着1个点至多只能与2个儿子相连,边不能相交就意味着1个点可以至多与4个儿子相连。

这时如果我们还用原本的做法,将状态扩充到\(s = 3\),会觉得组合的种类数过多,转移过于复杂。

考虑维护一些相对简单的状态,用它们组合成可能的结果。

考虑答案的2条路径与当前子树根节点\(x\)的关系。

  1. 2条路径由从\(x\)向下的4条直链组成。只需维护\(f(x)(0\rightarrow3)\)表示从\(x\)向下前4长的直链长度,用\(\sum _{i = 0}^{i = 3}f(x)(i)\)更新答案。

  2. 2条路径都不经过\(x\)

    要么这2条路径集中于同1个子树中,这种情况与\(x\)毫无关系,会在递归到子树\(y\)时求解。

    要么这2条路径分散于2个子树中,故维护\(d(x)\)表示以\(x\)为根的子树的直径,用\(d(y_1) + d(y_2)\)更新答案,其中\(y_1,y_2\)\(x\)的不同的2个儿子。

  3. 1条路径经过\(x\),另1条在某子树\(y\)中。对于当前访问的儿子\(y\)

    要么\(x\)\(y\)不相连,且\(y\)贡献1条路径,则用\(f(x)(0) + f(x)(1) + d(y)\)更新答案。

    要么\(x\)\(y\)相连,\(y\)除了贡献路径的半边,还贡献多1条不相交路径。考虑\(g(x)\)表示在以\(y\)为根的子树中,除了从\(x\)节点直下,还有1条与之不相交路径的最长总长度;\(g(x)\)在访问子树\(y\)时可用\(f(x)(0) + d(y)\)\(max(d(prey)) + f(y)(0) + 1\)\(g(y) + 1\)\(f(x)(0) + f(x)(1) + f(x)(2)\)进行维护。则用\(f(x)(0) + g(y) + 1\)更新答案。

    要么\(x\)\(y\)相连,\(y\)仅贡献路径的半边。则用\(g(x) + f(y)(0) + 1\)贡献答案。

值得注意的是,语句的顺序十分重要,当需要用到之前子树的信息时,不可以用已被\(y\)更新过的信息!

时间复杂度\(O(n)\)。[代码见此](https://github.com/littlewyy/OI/blob/master/luogu 3629 dp.cpp)。

posted @ 2019-10-26 12:10  littlewyy  阅读(159)  评论(0编辑  收藏  举报