WJX博客

学习笔记——树形DP

前言

学完树形\(DP\)\(NOIP\)会考的所有\(DP\)我们就都学完了,所以让我们一鼓作气,开始树形\(DP\)的学习之旅吧!

一、树形\(DP\)的基本概念

顾名思义,树形\(DP\)就是在树这种数据结构上进行\(DP\)(这不是废话吗?),通过有限次遍历树,记录相关信息,以求解问题。因为树形\(DP\)是建立在树上的,而树中的父子关系本身就是一个递归结构(满足子问题重叠性),所以自然而然地就衍生出了两种\(DP\)顺序:

1.叶子节点 $\Rightarrow $ 根节点:就是将叶子节点的信息向上传到父亲节点,在父亲节点处进行信息的整合。最后由根节点记录的信息得到最优解。

例如本题可以将下属来不来得到的权值作为信息传递到上级(题解)(我的代码)

2.根节点 $\Rightarrow $ 叶子节点:取所有点作为一次根节点进行求值,此时父节点得到整棵树的信息,只需要去除这个儿子节点\(DP\)值的影响,然后再转移给这个儿子。(此做法一般不用,但有时可能有奇效)

树形\(DP\)的顺序:一般按照\({\color{Red}\colorbox{White}{后续遍历}}\)的顺序(先处理儿子节点再处理当前节点(这样才符合\(DP\)的三个基本条件))

实现方式:树形\(DP\)一般用\({\color{Red}\colorbox{White}{记忆化搜索}}\)实现(既然是记搜当然用的是递归了)(类似于树剖的第一个dfs)

时间复杂度:一般的树形\(DP\)的时间复杂度是\(O(N)\)(因为每一个节点只会被遍历一次),如果有附加维\(M\),则复杂度为\(O(N\times M)\)

二、树形\(DP\)的几种类型

1.背包类树形\(DP\)

既然我们前面将背包和树形\(DP\)联系在一起学了,那我们就先从我们熟悉的背包类入手吧!(这样能增加我们的信心,减少我们对树形\(DP\)的畏惧心理)

1.先来一道入门题(树上01背包)(题解)(我的代码)

通过本题,我们就可以大致了解树形\(DP\)的思路--从根节点出发不断向子树进行搜索,再将子树的信息合并到该节点上。(注意:本题要考虑只选该节点与子树连接的这一条边的情况)

2.经典的树上01背包(题解)(我的代码)

本题我们又学会了一种思想--建虚点。因为本题是在森林中选一定的节点(选完父节点才可以选子节点),但我们又不可以只由一棵树得到最优解,所以我们可以考虑建一个虚点(\(0\)),令它向所有树的根节点连一条边,接着只要对以虚点为根节点的子树进行树形\(DP\)求出最优解即可。

3.细节较多的树上01背包(题解)(我的代码)

通过本题,我们要学会在树形\(DP\)时注意细节--该节点与它儿子节点相连的边到底可不可取,以后做树形\(DP\)题推导状态转移方程时要仔细考虑这个问题,才能更正确的做出树形\(DP\)题。

4.状态转移方程含义比较新奇的题(内层循环顺序讲解清楚的题解)(我的代码)

本题告诉我们:

(1)树形\(DP\)的状态转移方程记录的可以不只是一个最优解,还可以记录该状态对最终答案的贡献

(2)我们可以通过建立双向边的方式将一棵树强行转化为以1号节点为根节点的树,然后通过遍历顺序重新确立父子节点

(3)我们要注意该状态有没有被更新过,可以通过思考将一些不可能更新答案的状态排除(本题中还是初始值-1的状态),加快程序的运行效率。

5.(上一道搜索题(大雾)(讲的很详细的题解)(我的代码)

先吐槽一下:普及\(DP+IOI\)输入\(=\)提高难度

好了好了,来总结一下本题的收获

(1)联想已学:遇到另类的输入时,可以联想一下我们已学算法中有没有类似的结构。例如本题可以用类似线段树的建树方式来解决输入问题。

(2)根据题意合理运用填表法和刷表法

还是先简单解释一下这两个词吧!

填表法 :就是一般的动态规划,当前点的状态,可以直接用状态方程,根据之前点的状态推导出来。

刷表法:由当前点的状态,更新其他点的状态。

刷表法需要注意:只用于每个状态所依赖的状态对它的影响相互独立

这么说大家是不是还是一头雾水,举个例子:如果状态\(A\)对状态\(B\)有影响,状态\(C\)也对状态\(B\)有影响,当状态\(A\)的影响和状态\(C\)的影响相互不影响,就可以运用刷表法。

本题中就记录到达该走廊末尾需要花费的时间(设为\(tim\)),令\(j\)表示分配给左儿子的时间,\(k\)表示分配给右儿子的时间,然后更新以\(u\)为根节点的树花\(tim+j+k\)秒可以取得的最大价值。这样可以极大地减少思考难度。

继续挖坑 (挖坑太多后面会不会填不完啊)

1.P3354 [IOI2005]Riv 河流

2.P4322 [JSOI2016]最佳团体

3.P4037 [JSOI2008]魔兽地图

4.P4516 [JSOI2018]潜入行动

2.普通树形\(DP\)

经历过树形背包的洗礼,我们应该了解到了树形\(DP\)的基本思路,接下来就开始真正的树形\(DP\)吧!

1.最大权独立集问题(题解)(我的代码)

本题作为树形\(DP\)的经典例题,当然会在各种场合出现。本题要求父子节点二选一,使总价值最大,自然是一道典型的最大权独立集问题板子。做这类题,我们可以\(DP\)数组上多开一维,这一维只有\(0\)\(1\)组成,表示该节点选或者不选,然后在根节点处统计答案最大值即可。

2.最小权覆盖集问题(覆盖边)(题解)(我的代码)

本题要求 在给定的树上取最小的节点数,使所有边都至少有一个端点在选中的集合中。 我们还可以仿照第一问的思路继续解题-- \(DP\)数组上多开一维,表示该节点选或者不选,然后在根节点处统计答案最小权值 。所以,只要我们深刻理解树形\(DP\)的方法,就可以如法炮制做其他题。

3.最小权覆盖集带点权的问题(覆盖点)(题解)(我的代码)

本题看上去像例题2的加强版,但其实本题要求守点,而不是像例题2一样守边。就是这一个看似细小的差距,却导致了代码的千差万别。例如下面这张图:

如果我们只守1、4号点,那这一条链上的点就都可以守到,但2<->3这一条边明显没有守到。所以本题中我们要考虑三种情况:

1.\(x\)节点被自己覆盖,即选择\(x\)点来覆盖\(x\)

2.\(x\)节点被儿子\(y\)覆盖,即选择\(y\)点来覆盖\(x\)

3.\(x\)节点被父亲\(fa\)覆盖,即选择\(fa\)点来覆盖\(x\)

然后分类讨论进行状态转移,最后在根节点处取\(min(f[1][0],f[1][1])\)作为代价的最小值。(因为根节点没有父亲节点,所以不用讨论该情况)

CF1120D Power Tree(题解)(我的代码)

本题想到\(DP\)的思路并不难,但主要恶心在输出每个点是否可能成为最优解,这就导致一道本来黄\(\sim\)绿的树形\(DP\)题强行升紫。

思路:

我们用dp[u][0]表示以\(u\)为根节点的子树将所有叶子节点控制的最小代价,用
dp[u][1]表示以\(u\)为根节点的子树中还剩一个叶子节点未控制的最小代价。

  • \(dp[u][1] = \sum\limits dp[v][0] - max(dp[v][0] - dp[v][1])\)(我们可以贪心选择一颗子树,使\(dp[u][1]\)的代价最小)

  • \(dp[u][0] = min(sum[u],dp[u][1] + val[u])\)(显然,我们可以不选当前节点取尽\(u\)的所有子树,也可以选当前节点)

我们再建一堆辅助数组,用于输出答案,用g[u]表示\(u\)的儿子节点中dp[v][0]-dp[v][1]的最大值,用
num[u]表示dp[u][0]可以有多少个dp[v][1]转移而来,用sum[u]表示所有dp[v][0]之和,再用can[u][1]表示可以通过dp[u][1]转移到其父亲节点can[u][0]表示可以通过dp[u][0]转移到其父亲节点。

  • 如果\(can[u][0]=1\)

  • \(\circ\)转移到\(can[v][0]\)

  • \(\circ\) \(\circ\) 如果\(num[u]>1\),则\(can[v][0]=1\),因为\(v\)节点不一定传递到\(dp[u][1]\)

  • \(\circ\) \(\circ\) 如果\(g[u]!=dp[v][0]-dp[v][1]\),即\(v\)节点一定传递到\(dp[u][0]\)\(can[v][0]=1\)

  • \(\circ\) \(\circ\) 如果\(dp[u][0]==sum[u]\),即\(v\)节点一定传递到\(dp[u][0]\)\(can[v][0]=1\)

  • \(\circ\)转移到\(can[v][1]\)

  • \(\circ\) \(\circ\) 如果\(sum[u]-dp[v][0]+dp[v][1]+val[u]==dp[u][0]\),即贪心选择的子树是\(v\)节点,显然\(can[v][1]=1\)

  • 如果\(can[u][1]=1\)

  • \(\circ\)转移到\(can[v][0]\)

  • \(\circ\) \(\circ\) 如果\(num[u]>1\),则\(can[v][0]=1\),因为\(v\)节点不一定传递到\(dp[u][1]\)

  • \(\circ\) \(\circ\) 如果\(g[u]!=dp[v][0]-dp[v][1]\),即\(v\)节点一定传递到\(dp[u][0]\)\(can[v][0]=1\)

  • \(\circ\)转移到\(can[v][1]\)

  • \(\circ\) \(\circ\) 如果\(g[u]==dp[v][0]-dp[v][1]\),即贪心选择的子树是\(v\)节点,显然\(can[v][1]=1\)

又到了扔题时间

1.P4084 [USACO17DEC]Barn Painting G

2.P3174 [HAOI2009]毛毛虫

3.P3621 [APIO2007]风铃

4.P3574 [POI2014]FAR-FarmCraft

5.P3523 [POI2011]DYN-Dynamite

6.P4099 [HEOI2013]SAO

7.P3237 [HNOI2014]米特运输

3.换根树形\(DP\)

1.基本思路

  • 先以\(1\)号节点,进行一遍\(dfs\),令\(f[u]\)表示以\(u\)为根节点的子树的最优解。

  • 将根节点转移到其儿子节点上,并不断更新\(f[to]\)(假设\(to\)\(u\)的儿子节点)

  • 将第二步推而广之,逐渐更新完整颗树

由上面的操作可以看出,换根\(DP\)其实就是不断利用以\(1\)为根节点得到的信息更新以其他点为根节点得到的信息。

1.来一道入门题--找使所有结点的深度之和最大的根节点(题解)(我的代码)

作为入门题,当然是要让我们熟悉换根\(DP\)基本思路的题。本题可以令\(f[i]\)表示以\(i\)号节点为根节点的树的深度和,用根节点从\(u\)移动到\(to\)(\(u\)的儿子节点)时以\(to\)为根节点这颗子树的所有节点的深度\(-1\),而其他节点的深度\(+1\)进行状态转移。

2.找使所有节点到当前点最短距离的点(题解)(我的代码)

一样的板子和套路,不过因为极大值比较大,所以先以\(f[1]\)为极大值再更新其他值即可。(白白WA40pts)

3.求距离每个节点不超过k的点的权值和(题解)(我的代码)

4.找使该节点所有子树大小和最大的点(题解)(我的代码)

5.询问该节点经过改造后是否能成为重心(题解)(我的代码)

本题思路比较好想(改造该点子树中最大节点数不超过\(\frac{n}{2}\)的子树),但是码代码时一定要养成好习惯,不要打出int to=e[i].nxt这样的细节错误 (关键这种错误找了一个下午+一个晚上才找出来)

因为要去考\(NOIP\),所以还是挖坑 (估计也不会填了,毕竟考完还要补文化)

我回来填坑了\(!\)

1.P6419 [COCI2014-2015#1] Kamp(题解)(我的代码)

2.P3647 [APIO2014]连珠线(题解)(我的代码)

posted @ 2021-08-05 15:59  WJX3078  阅读(191)  评论(0)    收藏  举报