树形dp学习笔记

· · 个人记录

树形dp学习笔记

注意,因为讲课人的水平实在太菜,这次的题目平均难度只有CF2000分。

树是一种十分甚至九分优美的结构,其本身子结构的性质让dp变得很可行。

从根节点出发,向儿子做深度优先搜索,用子节点的答案来合并更新父节点的答案,从而得到全局的答案。

也可以再从根到叶子节点更新以获取答案,这样的话前面的操作就可以认为是预处理。实际上感觉大多数树形dp都需要两次dfs

一般的树形dp可以把子树内的点和子树外的点分开考虑计算结果。

树形dp的状态多且杂。

一、普通树上dp

练习:

  1. 洛谷p1352没有下司的舞会

经典的树形dp了。不会的建议删号重练。

code

  1. 洛谷P8867建造军营

然后是大家非常熟悉和喜爱\text{NOIP2022 T3},这道题需要先对图做边双,把它变成一棵树,再进行dp。
f[i] 表示 i 的子树里至少选了一个点,且所选的点到 i 的边都被选了的方案数。设 a[i] 表示子树里的桥边数量, b[i] 表示有多少点被缩到 i 里。
那么对于 f[u] 存在:

code

二、树上背包

树上背包和普通背包不同点在于物品之间有相互的依赖关系。选取一个物品的必要条件是选取了所有他的所有祖先,因此只需要考虑选取当前物品对后续的影响即可。

练习:

  1. 洛谷P2014 [CTSC1997] 选课

01背包的模板,设 dp[i][j] 为第 i 号节点的子树中有 j 个连续的节点被选了,而对于每一个内部有点被选的子树,其根节点都是必选的,所以可以把 dp[i][1] 设为该节点的权值,然后做背包就可以了。
注意,单次背包合并的复杂度是 O(n^2) ,需要合并 n 次,所以总复杂度为 O(n^3)其实不优化也能通过这道题。 但是我们可以舍弃一些无用的状态来优化dp。

这样就控制了 jk 的上下界:

$k$ 的下限为 $max(0,j-size[u])$ ,上限为 $min(size[v],j)$ 。 每个点对只会在他们的 $lca$ 处合并一次,所以复杂度为 $O(n^2)$ 。 [code](https://www.luogu.com.cn/paste/mtw69cvv) 2. [P4516 [JSOI2018] 潜入行动 ](https://www.luogu.com.cn/problem/P4516) 设 $dp[x][i][1/0][1/0]$ 表示以 $x$ 为根的子树中一共放了 $i$ 个监听装置,以 $x$ 为根的子树中除 $x$ 外其他节点都能被监听,其中 $x$ 点放/没放装置,是/否被监听的方案数。 考虑这样的情况,其中 $v$ 是 $x$ 的子节点。 ![](https://cdn.luogu.com.cn/upload/pic/41854.png) - 如果 $x$ 没有被监听,没有放装置,那么 $v$ 一定没有放装置,所以 $dp[x][i+j][0][0]=\sum dp[x][i][0][0]*dp[v][j][0][1]$ 。 - 如果 $x$ 没有被监听,放了装置,那么 $v$ 是否被监听就无所谓了,但是一定没有放装置,所以 $dp[x][i+j][1][0]=\sum dp[x][i][1][0]*(dp[v][j][0][0]+dp[v][j][0][1])$ 。 - 如果 $x$ 被监听了,没有放装置,需要分情况。 1. $x$ 的状态是 $dp[x][i][0][1]$ ,这时因为 $x$ 已经被监听了,所以 $v$ 是否放装置就随便了,但是它一定要被监听,所以贡献是 $dp[x][i][0][1]*(dp[v][j][0][1]+dp[v][j][1][1])
  1. 所以 $dp[x][i+j][0][1]=\sum(dp[x][i][0][1]*(dp[v][j][0][1]+dp[v][j][1][1])+dp[x][i][0][0]*dp[v][j][1][1])$ 。 - 如果 $x$ 被监听了,放了装置,同样分情况
  2. 所以 $dp[x][i+j][1][1]=\sum(dp[x][i][1][0]*(dp[v][j][1][0]+dp[v][j][1][1])+dp[x][i][1][1]*(dp[v][j][0][0]+dp[v][j][0][1]+dp[v][j][1][0]+dp[v][j][1][1]))$ 。 然后就可以做dp了。

code

三、换根dp

树形dp中的换根dp问题又被称为二次扫描,通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响。

一般来讲,换根dp需要先做一次dfs预处理,再更新答案,且用父节点更新子节点或用子节点更新父节点都是 O(1) 完成。

练习:

  1. P3478 [POI2008] STA-Station

给出一个n个节点的树,找出一个节点为根,使得树上所有节点的深度之和最大。
我们先假设1号节点就是根节点,这时候,我们对树做一次dfs就可以求出每个点的子树的大小,并且同时也可以求出所有节点的深度之和。
这时候,如果我们把根节点转移到1号节点的一个儿子x上,那么x节点对应的所有以1为根节点的子树中的节点的深度都要减去1,而除了x以外的其他儿子节点和1节点的深度都要增加1,所以对应总的深度和就可以计算出来,也就是说通过父节点信息我们可以推算出子节点的信息,这样我们就可以在树上进行动态规划了。

code

  1. P6419 [COCI2014-2015#1] Kamp

如果我们能算出对于一个点 u ,从它出发送完所有人再回到 u 的最短距离,再减去到这个点的最长链的长度,就是答案。

code

四、虚树

有时我们会遇到一些多次询问的树形dp,当每一次询问只涉及树上少量节点时,我们并不需要对整棵树进行dp。我们可以建立一棵仅包含少数关键点的虚树,将非关键点收缩成边或者直接删去,这样可以减少时间上的消耗。

如何建立虚树

最右链是虚树构建的一条分界线,意为在最右链左侧的点都已经建好虚树。我们可以用一个栈 sta 来维护最右链, top 为栈顶。

注意,最右链上的边并没有被加入虚树,因为随时都可能有点被弹出。

假设现在已经考虑到了 now 号节点, nowsta[top] 的lca是 lc
由于 lcsta[top] 的祖先,所以它一定在我们维护的最右链上。
分情况讨论:

  1. ![](https://cdn.luogu.com.cn/upload/pic/52299.png) 此时直接把 $now$ 加在 $sta$ 的末尾即可。
  2. ![](https://cdn.luogu.com.cn/upload/pic/52300.png) 此时 $sta[top]$ 已经不在最右链里了,所以把边 $lc-sta[top]$ 加入虚树,然后把 $sta[top]$ 出栈,把 $lc$ 、 $now$ 加入最右链。
  3. ![](https://cdn.luogu.com.cn/upload/pic/52301.png) 跟上一种其实是一样的,但是 $lc$ 不用再加入最右链了。
  4. ![](https://cdn.luogu.com.cn/upload/pic/52404.png) 此时我们需要把 $lc-sta[top]$ 之间的所有边都加入虚树,再全部出栈,最后加入 $lc$ 和 $now$ 。

对于每一个询问,我们需要的虚树也不同,但是不能直接对虚树进行清空,这样的复杂度会退化到 O(n) ,只要在dfs时一边访问一边清空就好了。

练习:

  1. P2495 [SDOI2011] 消耗战

我们可以对每个点预处理出 mn[u] 表示 u1 的路径上最小的边权。
对每次询问建立虚树,如果 u 是询问的点,那么切断 u 与其子树上的询问点的最小花费 dp[u]=mn[v] ,否则 dp[u]=min(mn[u],\sum\limits_{v\in child(u)}dp[v])

code

一些题目

P8916 [DMOI-R2] 暗号

题目大意:有一棵树,给一棵 n 个节点的树,每个节点有一个权值。要求给这 n 个节点黑白染色。两个拥有相同颜色的节点中,深度较小的结点的权值加上子树中颜色与它相等的节点初始值,求权值和的最大值。
长沙王讲过就不讲了。
dp[u][j][k][0/1] 表示 u 到根的路径上 j 个连续的黑色, k 个连续的白色, u 节点染成黑或白。
那么

dp[u][j][k][0]=\sum\limits_{v\in child(u)}max(dp[v][j][k][1],dp[v][j+1][k][0])+(j+1)*w[u] dp[u][j][k][1]=\sum\limits_{v\in child(u)}max(dp[v][j][k][0]+dp[v][j][k+1][1])+(j+1)*w[u]

P4099 [HEOI2013]SAO

题目大意:有一个 n 个节点的树形图,求它有多少中拓扑序。
想容斥的同学自己切掉就好了,不要声张。
dp[u][i] 表示在 u 的子树里 u 的排名为 i 的方案数。
我们让 u 和他儿子 v 合并,也就是用 dp[v][j] 来更新 dp[u][i]
有两种情况:

  1. 枚举 $k$ 表示 $v$ 的子树里有 $k$ 个在 $u$ 前面,所以: $$dp[u][i+j]+=dp[u][i]*dp[v][k]*C_{i+j-1}^{i-1}*C_{siz[u]+siz[v]-i-j}^{siz[u]-i}$$ 用个前缀和就可以把 $k$ 这一维删掉。
  2. 和1差不多,不想讲了。
    同样用后缀和可以删掉一维。

CF337D Book of Evil

题目大意:一棵 n 个节点的树,有一些点被标记,问有多少点离所有被标记的点的距离都小于 d
dp[u][0] 表示从 u 到其子树里最远的被标记的点的距离, dp[u][1] 表示从 u 到其另一个儿子的子树里最远的被标记的点的距离。
dis[u] 表示从 u 到其子树以外的被标记的点的最远距离。

``` dis[v]=max(dp[v][0]+1==dp[u][0]?dp[u][1]:dp[u][0],dis[u])+1 ``` [CF581F Zublicanes and Mumocrates ](https://www.luogu.com.cn/problem/CF581F) 题目大意:给一棵 $n$ 个节点的树染上黑白两种颜色,使度数为1的点刚好一半是黑点,问最少多少条边的两个顶点颜色不同。 设 $f[u][i][0/1]$ 表示 $u$ 的子树里,有 $i$ 个叶子结点为黑,当前点染成黑/白的最小值。 那么枚举 $v$ 中有 $j$ 个叶子染成黑,就有: $$f[u][i+j][0]=f[u][i][0]+\sum\limits_{v\in child(u)}min(f[v][j][0],f[v][j][1]+1)$$ $$f[u][i+j][1]=f[u][i][1]+\sum\limits_{v\in child(u)}min(f[v][j][0]+1,f[v][j][1])$$ 注意边界,对于叶子结点 $u$ , $f[u][0][0]=f[u][1][1]=0$ 。 [P3647 [APIO2014] 连珠线](https://www.luogu.com.cn/problem/P3647) [P3237 [HNOI2014] 米特运输](https://www.luogu.com.cn/problem/P3237) [P2607 [ZJOI2008] 骑士](https://www.luogu.com.cn/problem/P2607) [P3574 [POI2014] FAR-FarmCraft ](https://www.luogu.com.cn/problem/P3574)