树上 DP(树形 DP & 换根 DP)
以前咋这么多鸽子呃呃呃啊啊啊啊啊啊啊啊
树形 DP
基本形式:
f[u] 表示 u 为根的子树内,xxx 值。这样可以把一颗子树的信息压到一个点内。
所以在计算 f[u] 时,u 通过孩子 v 转移过来更新就好,此时 v 对 u 更新,相当于是点对点的更新(都把子树压成了点)。
儿子 v 对 u 的贡献独立(可能绝大多数但不完全是),考虑一颗子树如何转移过来即可,其余同理。
常见的普通树形 DP 例题
一些基本的树形 DP 例题(还有以前写的,但是修过一遍,完全是能看的):树形dp - cn是大帅哥886 - 博客园
P3155 (Code),其实很水,但是想了很久还不会,就是转移方程定的比较模糊,在转移时多从状态出发思考,考虑转移点可能是哪些状态,转移过来。
然后此题不用换根,是因为,题目相当于告诉你,顺着叶子往上走,遇着的第一个颜色是什么。只是跟叶子有关。然后叶子是不会变的,所以这东西和根无关。
树上染色。一个树形 DP 计数题。
树上背包
也是树形 DP 的一种。
例题:P2014(可以看以前的 blog 里有详细分析,这里提炼一下精华)
可以把若干棵树看成物品,于是有了 dp定义:f[u][i][j]:u子树内,前 i 棵子树,选了重量为 j 的最大价值。
但是树形 DP 是顺次遍历儿子, i 这一维可以滚动掉。
这里可以延伸,树形 DP 计算 f[u] 每次相当于加入一颗子树 v,和先前的若干个子树合并贡献到 u,在没更新 u 之前,先前的若干棵子树算出的结果即为 u。
此处又顺应了上面说的,所以只需要考虑单个儿子 v 对 u 会有什么贡献即可。
更多例题:
P2015(Code),和选课差不多,把边权转成点权就好了。不过要注意边界,多考虑状态的边界的值是什么。
不仅要记录子树内的信息,为了方便转移,还需考虑记录子树内向外延伸的信息
P2016:树上,每个点有代价,选了一个点,就能让这个点能覆盖到和这个点相邻的边,要求全部边被覆盖最小代价。
考虑边 (u,v),在考虑到 u 点时,想要边被覆盖,只可能是选 u 或选 v。于是考虑 u 或者 u 的儿子选不选即可。
P2458(最小支配集):树上,每个点有代价,选了一个点,就能让这个点能覆盖到和这个点相邻的点,要求全部点:被覆盖最小代价。
在考虑到 u 点时,想要 u 被覆盖,只可能是选 v 或选 fa。于是考虑 u 或者 u 的儿子或 u 的父亲选不选即可。
所以会定 f[u][0/1/2] 表示 u 子树内的点都被覆盖所花费的最小代价,其中 u 是被自己/儿子/父亲 覆盖的。
然后注意一下边界就好。
设计转移方程时,要考虑树形 DP 本质,根据状态定义,不断划分子问题。仍然考虑 v 转移到 u,即使 u 有涉及到 fa,但不能用 fa 更新 u。
这样引出一个问题
当 u 被父亲覆盖时,为什么不用把选父亲的代价加上?加了反而错,不加就对?
因为选父亲的代价在父亲的位置被计算。你现在加了,相当于加重了,当然就错了。之后在父亲位置时自然会被转移方程考虑到。
所以计算当前子树不用考虑向外延伸的代价。只需要考虑子树内的代价,因为向外延伸的代价在之后自然会被考虑。
更多例题:
士兵的放置(Code)上题带了计数版本。因为要计数,注意把重复的状态要去掉,即一个点既被父亲,又被儿子覆盖。
出现这种情况,只需要在计算被儿子覆盖的时候算上,被父亲覆盖的时候就不用算了。
另外为了方便计数,可以改变一下 f[u][1] 转移方程。只需满足 u 的一个儿子必选自己,就行了。可以把转移当前会每次增加一颗子树进来。
可以参考的题解:【bzoj2314】士兵的放置
AT_abc259_f(Code),类似背包,但是时间空间不允许。发现 u 连出的边一定是有1或0条 (u, fa) 和若干条 (u, v),所以记 f[u][0/1],表示 u 有没有连向父亲的边。然后贪心转移就好。
刚开始想的差不多了,但差一点,如果状态非法可以直接设成 INF,而不是说不从该状态转移,这样可能会有些问题。
换根 DP
何时用换根?可以现场分析一下,答案是否跟根是哪个结点有关。
对于根可能变的问题如果只是路径上的问题那么跟根没有关系不需要换根,如果是子树之类与根关联的问题就要换根。
比如说问每个节点深度和,这个问题和哪个点当根关联性很大,需要换根。
你会发现换根这是相当于改变了原来树的父子关系。
当要求的信息都包括 u 点,不妨考虑成是以 u 为根时的答案。
学习笔记看这个,很详细了:换根dp - cn是大帅哥886 - 博客园
鸽子
没错还是鸽子。
再挂一次:图树模板 - 题单 - 洛谷 | 计算机科学教育新生态
树形 DP+换根 题单:云剪贴板 - 洛谷 | 计算机科学教育新生态
NFLS 树形DP+换根 题目+代码: