树形 DP
树形 DP,就是以树为模型的动态规划。主要利用树本身带来的子结构(树上的父子关系)来进行状态转移。
一般情况下,都是通过子节点的 DP 值来推出父节点的 DP 值。除此之外,经常会通过子节点的一些辅助信息来推出父节点的辅助信息,比如
状态设计
常见类型:
注:为了方便阅读,做如下规定:
- 下文中
" role="presentation"> 统一表示 a x x " role="presentation"> 的点权。s " role="presentation"> 统一表示 z x x " role="presentation"> 的子树大小。l s o " role="presentation"> 和 n x r s o " role="presentation"> 分别表示 n x x " role="presentation"> 的左右儿子。(仅限于二叉树)s o " role="presentation"> 表示 n x x " role="presentation"> 的儿子。f " role="presentation"> 表示 a x x " role="presentation"> 的父亲。w ( u , v ) " role="presentation"> 表示u → v " role="presentation"> 这条边的权值。
-
以
d " role="presentation"> 表示以p x x " role="presentation"> 为根的答案。一般
x " role="presentation"> 的答案可以直接由x " role="presentation"> 的儿子转移得到。较为简单。定义
d " role="presentation"> 表示以p x x " role="presentation"> 为根的子树中最大的子树权值和是多少。特征:类比最大子段和,若以
x " role="presentation"> 儿子为根的d p " role="presentation"> 值> 0 " role="presentation">,则说明其对x " role="presentation"> 有贡献,反之无贡献,即x " role="presentation"> 的d p " role="presentation"> 值与其儿子的d p " role="presentation"> 值有关。转移:
d p x = a x + ∑ max ( d p s o n [ x ] , 0 ) " role="presentation"> -
以
d " role="presentation"> 表示以p x , 0 / 1 x " role="presentation"> 为根的子树,x " role="presentation"> 选或者不选/x " role="presentation"> 是否在某个特定类型中(题目给出)的答案。首先子结构往往和树上父子关系有关,往往答案与
x " role="presentation"> 选/不选或者某个题中所给的特定类型有关,且第二维一般表示x " role="presentation"> 的状态。详见下述例子。例:
定义
d " role="presentation"> 表示以p x , 0 / 1 x " role="presentation"> 为根的子树,x " role="presentation"> 参加/不参加舞会的最大的快乐指数。特征:
x " role="presentation"> 为根的子树的答案可以由x " role="presentation"> 的儿子转移而来,x " role="presentation"> 的儿子参加或不参加决定了x " role="presentation"> 是否参加。转移:
x " role="presentation"> 如果参加,x " role="presentation"> 的儿子参加或者不参加都行。x " role="presentation"> 如果不参加,那么x " role="presentation"> 的儿子一定不能参加。d p x , 0 = ∑ max ( d p s o n [ x ] , 0 , d p s o n [ x ] , 1 ) d p x , 1 = a x + ∑ d " role="presentation">p s o n [ x ] , 0 几乎和没有上司的舞会一样,只不过是在基环树上,我们只需要枚举断哪条边即可。不做赘述。
定义
d " role="presentation" style="position: relative;"> 表示以p x , 0 / 1 x " role="presentation" style="position: relative;"> 为根的的子树,x " role="presentation" style="position: relative;"> 是否包含在一个有黑点的连通块中。特征:
x " role="presentation" style="position: relative;"> 为根的子树答案可以由x " role="presentation" style="position: relative;"> 的儿子转移而来,x " role="presentation" style="position: relative;"> 的儿子所在连通块是否有黑点决定了x " role="presentation" style="position: relative;"> 的方案数。转移:
x " role="presentation" style="position: relative;"> 所在连通块如果没有黑点,那么有黑点的子树必须跟x " role="presentation" style="position: relative;"> 联通,无黑点的子树也必须跟x " role="presentation" style="position: relative;"> 联通。x " role="presentation" style="position: relative;"> 所在连通块如果有黑点,那么有黑点的子树一定要跟x " role="presentation" style="position: relative;"> 断开,无黑点的子树必须要跟x " role="presentation" style="position: relative;"> 联通。d p x , 0 = d p x , 0 × d p s o n [ x ] , 0 + d p x , 0 × d p s o n [ x ] , 1 + d p x , 0 × d p s o n [ x ] , 1 d p x , 1 = d p x , 1 × d p s o n [ x ] , 0 + d p x , 1 × d p s o n [ x ] , 1 + d p x , 0 × d " role="presentation" style="position: relative;">p s o n [ x ] , 1 d p " role="presentation" style="position: relative;"> 表示以1 x , 0 / 1 / 2 x " role="presentation" style="position: relative;"> 为根的子树,x " role="presentation" style="position: relative;"> 染成绿/红/蓝色时,有多少个点最多能被染成绿色。d p " role="presentation" style="position: relative;"> 相应的表示最少有多少个点被染成绿色。2 x , 0 / 1 / 2 特征:
x " role="presentation" style="position: relative;"> 被染成什么颜色由x " role="presentation" style="position: relative;"> 的儿子和x " role="presentation" style="position: relative;"> 的兄弟决定,且与儿子个数有关。转移:
这里以
d p 1 " role="presentation" style="position: relative;"> 的转移为例,d p 2 " role="presentation" style="position: relative;"> 完全可以类比。当
x " role="presentation" style="position: relative;"> 染绿色时,x " role="presentation" style="position: relative;"> 的儿子只能染红色/蓝,若x " role="presentation" style="position: relative;"> 有一个儿子,则无所谓,反之它的两个儿子必须染不同的颜色。答案为儿子中答案的最大值 +1。当
x " role="presentation" style="position: relative;"> 不染绿色时,x " role="presentation" style="position: relative;"> 的儿子只需要不与x " role="presentation" style="position: relative;"> 颜色相同,分别讨论x " role="presentation" style="position: relative;"> 为红/蓝的情况,分别讨论即可。答案为儿子中答案的最大值。x " role="presentation" style="position: relative;"> 无儿子时:d p x , 0 = d p x , 1 = d p x , 2 = 1 " role="presentation" style="text-align: center; position: relative;">x " role="presentation" style="position: relative;"> 只有一个儿子时:d p x , 0 = max ( d p s o n [ x ] , 1 , d p s o n [ x ] , 2 ) + 1 d p x , 1 = max ( d p s o n [ x ] , 0 , d p s o n [ x ] , 2 ) d p x , 2 = max ( d p s o n [ x ] , 0 , d p s o n [ x ] , 1 ) " role="presentation" style="position: relative;">x " role="presentation" style="position: relative;"> 有两个儿子时:d p x , 0 = max ( d p l s o n [ x ] , 1 + d p r s o n [ x ] , 2 , d p l s o n [ x ] , 2 + d p r s o n [ x ] , 1 ) + 1 d p x , 1 = max ( d p l s o n [ x ] , 0 + d p r s o n [ x ] , 2 , d p l s o n [ x ] , 2 + d p r s o n [ x ] , 0 ) d p x , 2 = max ( d p l s o n [ x ] , 0 + d p r s o n [ x ] , 1 , d p l s o n [ x ] , 1 + d p r s o n [ x ] , 0 ) " role="presentation" style="position: relative;">d " role="presentation" style="position: relative;"> 表示以p x , i x " role="presentation" style="position: relative;"> 为根的子树,x " role="presentation" style="position: relative;"> 填i " role="presentation" style="position: relative;"> 时,总价值最小是多少。发现
d " role="presentation" style="position: relative;"> 只能由p x , i d " role="presentation" style="position: relative;"> 中满足p s o n [ x ] , j i ≠ j " role="presentation" style="position: relative;"> 且值最小的答案转移而来,而符合这两个条件的只会是d " role="presentation" style="position: relative;"> 中的最大或次大值:当p s o n [ x ] , j i " role="presentation" style="position: relative;"> 本来就不与最大值的j " role="presentation" style="position: relative;"> 相等时就是最大,反之是次大。那么我们只需要定义另一个
t " role="presentation" style="position: relative;"> 用来表示p x , 0 / 1 x " role="presentation" style="position: relative;"> 子树内d p " role="presentation" style="position: relative;"> 值的最大/次大值即可。同时记录一下满足d " role="presentation" style="position: relative;"> 的p x , i d p " role="presentation" style="position: relative;"> 值中对应的最大值的i " role="presentation" style="position: relative;"> 是多少,记为m " role="presentation" style="position: relative;">i x 转移:
d p x , i = ∑ [ m i x ≠ i ] t p s o n [ x ] , 0 + ∑ [ m i x = i ] t " role="presentation" style="text-align: center; position: relative;">p s o n [ x ] , 1 拓展:此类树形 DP 求树的直径。
我们用
d " role="presentation" style="position: relative;"> 表示从p x , 0 / 1 x " role="presentation" style="position: relative;"> 出发的最长链/次长链。然后 dfs,考虑每条边加进来之后是否会影响最大/次大值,直接转移即可。
-
和区间 DP 结合,以
d " role="presentation" style="position: relative;"> 表示树中p l , r [ l , r ] " role="presentation" style="position: relative;"> 的答案。往往此类型的状态设计,类似区间 DP,会以区间的左右端点来描述状态,一个状态由比它更小的状态转移而来。
例:
定义
d " role="presentation" style="position: relative;"> 表示树中区间p l , r [ l , r ] " role="presentation" style="position: relative;"> 加分最大值。我们需要枚举对于当前区间
[ l , r ] " role="presentation" style="position: relative;"> 作为一个子树时,哪个点作为根,以区间的左右端点[ l , r ] " role="presentation" style="position: relative;"> 分别为该子树内的节点下标最大和最小值,运用了同一子树内节点下标一定连续的这一特征,同时枚举根,也利用了区间 DP 枚举中间节点的套路。同时也启发我们,遇到二叉树三种遍历的题,可以考虑使用类似合并区间的方法,考虑对于一个区间而言它的答案是什么。转移:
d p l , r = max ( d p l , r , d p l , k − 1 + d p k + 1 , r + d p k , k ) " role="presentation" style="text-align: center; position: relative;"> -
换根 DP。用
d " role="presentation" style="position: relative;"> 表示整棵树以p x x " role="presentation" style="position: relative;"> 为根时的答案。基于树形 DP 的基本模型,题目初始时不给出根节点,且我们需要知道每个点为根时的答案,暴力的做法是
O ( n 2 ) " role="presentation" style="position: relative;">。换根 DP 可以将复杂度降低O ( n ) " role="presentation" style="position: relative;">,只需要两遍 dfs,第一遍以 1(或者任选一个节点)为根,在根节点切换时,直接通过一些已经计算过的数据在O ( 1 ) " role="presentation" style="position: relative;"> 就能得到另一个根的结果。题目特征:需要选择一个点作为关键点/建立基站,问其它点到该关键点总的代价最大/最小是多少。
例:
首先以
1 " role="presentation" style="position: relative;"> 为根求出来所有的深度和。再定义d " role="presentation" style="position: relative;"> 表示p x x " role="presentation" style="position: relative;"> 作为整棵树的根时,所有节点的深度和。转移:
d p x = d p f a [ x ] − s z x + ( s z 1 − s z x ) " role="presentation" style="text-align: center; position: relative;">应该是换根 DP 的板子题了。
P2986 [USACO10MAR] Great Cow Gathering G
其实相当于求树的重心。我们也可以通过换根的方法来求。
定义
d " role="presentation" style="position: relative;"> 表示以p x x " role="presentation" style="position: relative;"> 作为整棵树的根时,所有奶牛要走的路程之和是多少。第一遍 dfs 从
1 " role="presentation" style="position: relative;"> 开始求出当1 " role="presentation" style="position: relative;"> 为根时的不方便程度。第二遍 dfs 以1 " role="presentation" style="position: relative;"> 为基准求出其它点的不方便程度。 " role="presentation" style="position: relative;"> 用于第一遍 dfs 的转移,f x d " role="presentation" style="position: relative;"> 用于第二遍 dfs 的转移。p x f x = ∑ f s o n [ x ] + s z s o n [ x ] × w ( x , s o n [ x ] ) d p x = d p f a [ x ] − d p x × w ( x , f a [ x ] ) + ( s z 1 − s z x ) × w ( x , f a [ x ] ) " role="presentation" style="position: relative;">P3047 [USACO12FEB]Nearby Cows G
这道题我写了题解。就不在这里多赘述了。
-
树上背包。一般用
d " role="presentation" style="position: relative;"> 表示对于以p x , i x " role="presentation" style="position: relative;"> 为根的子树,选择了i " role="presentation" style="position: relative;"> 个点的答案。选择i " role="presentation" style="position: relative;"> 个点干什么一般要根据具体题意而定。答案就是问题,题目中要求什么这个d p " role="presentation" style="position: relative;"> 状态就表示什么。我们一般会枚举x " role="presentation" style="position: relative;"> 的子树中选了多少个点以及s o n [ x ] " role="presentation" style="position: relative;"> 选了多少个点,再按照背包的方式转移。题目特征:一般而言,答案一般会与子树内选多少条边/多少个点有关,且
d " role="presentation" style="position: relative;"> 可以由p x , i d " role="presentation" style="position: relative;"> 推出,转移类似于背包。p y , j 例:
定义
d " role="presentation" style="position: relative;"> 表示的是以p x , i x " role="presentation" style="position: relative;"> 为根的子树,有i " role="presentation" style="position: relative;"> 条边被保留时,最多能留住的苹果的数量。我们可以枚举
s o n [ x ] " role="presentation" style="position: relative;"> 子树中保留了多少条边,假设s o n [ x ] " role="presentation" style="position: relative;"> 子树中保留了j " role="presentation" style="position: relative;"> 条边,x " role="presentation" style="position: relative;"> 子树中保留了i " role="presentation" style="position: relative;"> 条边,那么d " role="presentation" style="position: relative;"> 就可以由p x , i d " role="presentation" style="position: relative;"> 和p x , i − j − 1 d " role="presentation" style="position: relative;"> 转移而来,之所以要减一,是因为p s o n [ x ] , j i → j " role="presentation" style="position: relative;"> 这条边必须保留。转移:
d p x , i = max ( d p x , i , d p x , i − j − 1 + d p s o n [ x ] , j + w ( x , s o n [ x ] ) ) " role="presentation" style="text-align: center; position: relative;">定义
d " role="presentation" style="position: relative;"> 表示以p x , i x " role="presentation" style="position: relative;"> 为根的子树内,选择了i " role="presentation" style="position: relative;"> 门课程能获得的最大学分是多少。可以枚举每一个
s o n [ x ] " role="presentation" style="position: relative;"> 子树内选择了多少门课程,转移与上题类似,不做赘述。转移:
d p x , i = max ( d p x , i , d p x , i − j + d p s o n [ x ] , j ) " role="presentation" style="text-align: center; position: relative;">定义
d " role="presentation" style="position: relative;"> 表示走到节点p x , i x " role="presentation" style="position: relative;"> 时,剩余i " role="presentation" style="position: relative;"> 的时间,最多能拿到的画的数量。由于此题模型是二叉树,所以我们只需要枚举分给左儿子的时间,那么分给右儿子的时间就是
i " role="presentation" style="position: relative;"> 减去分给左儿子的时间。由于通过走廊还需要时间(即点有点权),所以还需减去 " role="presentation" style="position: relative;">。a x 转移:
d p x , i = max ( d p x , i , d p l s o n [ x ] , j + d p r s o n [ x ] , i − j − a [ x ] ) " role="presentation" style="text-align: center; position: relative;">定义
d " role="presentation" style="position: relative;"> 表示以p x , i x " role="presentation" style="position: relative;"> 为根的子树内,选了i " role="presentation" style="position: relative;"> 个用户的最大收益。枚举
s o n [ x ] " role="presentation" style="position: relative;"> 子树内选了多少个用户,然后考虑转移。最后同样要减去w ( x , a [ x ] ) " role="presentation" style="position: relative;">,表示这条边的花费。转移:
d p x , i = max ( d p x , i , d p x , i − j + d p s o n [ x ] , j − w ( x , a [ x ] ) ) " role="presentation" style="text-align: center; position: relative;">定义
d " role="presentation" style="position: relative;"> 表示以p x , i x " role="presentation" style="position: relative;"> 为根的子树 ,有i " role="presentation" style="position: relative;"> 个点染成了黑色的最大收益。这道题我写了题解。就不在这里多赘述了。
-
上述第二种和第四种相结合,同时需要记录
x " role="presentation" style="position: relative;"> 选或者不选以及子树中选择多少个的问题特征。一般定义
d " role="presentation" style="position: relative;"> 表示p x , i , 0 / 1 x " role="presentation" style="position: relative;"> 为根的子树内,选择了i " role="presentation" style="position: relative;"> 个点/进行了i " role="presentation" style="position: relative;"> 次操作,x " role="presentation" style="position: relative;"> 选/不选的答案。定义
d " role="presentation" style="position: relative;"> 表示p x , i , 0 / 1 x " role="presentation" style="position: relative;"> 为根的子树内,走了i " role="presentation" style="position: relative;"> 步,是否回头的最多能经过的格点数量。简要说明一下为什么要加最后一维。
如果我们直接定义
d " role="presentation" style="position: relative;"> 表示以p x , i x " role="presentation" style="position: relative;"> 为根的子树内走了i " role="presentation" style="position: relative;"> 步最多能经过的格点数量,那么有两种可能:- 从
x " role="presentation" style="position: relative;"> 的子树中绕几个圈圈后回到x " role="presentation" style="position: relative;">; - 在
x " role="presentation" style="position: relative;"> 的子树中绕几个圈圈后在向某一个子树一路走到死。
发现两种情况不能合并处理。所以我们要多记录一维是否回头(回头为
1 " role="presentation" style="position: relative;">,不回头为0 " role="presentation" style="position: relative;">)。对于
d " role="presentation" style="position: relative;">:由于到p x , i , 1 x " role="presentation" style="position: relative;"> 一定回头,所以s o n [ x ] " role="presentation" style="position: relative;"> 也必须回头,即只能从d " role="presentation" style="position: relative;"> 转移而来,转移类似于树上背包:p s o n [ x ] , i , 1 d p x , i , 1 = max ( d p x , i , 1 , d p x , i − j − 2 , 1 + d p s o n [ x ] , j , 1 ) " role="presentation" style="text-align: center; position: relative;">之所以减去
2 " role="presentation" style="position: relative;"> 是因为来回要走两次x → s o n [ x ] " role="presentation" style="position: relative;"> 这条边。对于
d " role="presentation" style="position: relative;">:发现对于p x , i , 0 s o n [ x ] " role="presentation" style="position: relative;"> 来说有三种可能:- 在
s o n [ x ] " role="presentation" style="position: relative;"> 之前就一路走到死; - 在
s o n [ x ] " role="presentation" style="position: relative;"> 处一路走到死; - 在
s o n [ x ] " role="presentation" style="position: relative;"> 之后一路走到死;
发现第三种情况会被前两种覆盖,所以我们只需要考虑前两种情况。
对于第一种情况,相当于要从上一个没有一路走到死的状态转移过来,即:
d p x , i , 0 = max ( d p x , i , 0 , d p x , i − j − 1 , 1 + d p s o n [ x ] , j , 0 ) " role="presentation" style="text-align: center; position: relative;">因为只会走
x → s o n [ x ] " role="presentation" style="position: relative;"> 这条边一次所以只需要减1 " role="presentation" style="position: relative;">。对于第二种情况,相当于要从上一个已经一路走到死的状态转移过来,即:
d p x , i , 0 = max ( d p x , i , 0 , d p x , i − j − 2 , 0 + d p s o n [ x ] , j , 1 ) " role="presentation" style="text-align: center; position: relative;">分别考虑转移即可。
- 从
后记
写于 NOIP2022 前。当我正在写这篇总结的时候,西安封城了,NOIP 大概率取消。那么是否非省队选手就直接无奖退役了呢,我不曾知晓,当然我大概率也是他们中的一员。
2022 年疫情因素众多,我们无法知晓未来发生的种种事情,也许会说”两年 OI 一场空,三天疫情见祖宗“,我起初也是这么觉得的,但现在突然就释然了,一切交给时间吧,做好我本来应该做好的事情就好了。
upd:陕西 NOIP2022 取消。