动态规划模型总结之树形dp

本文介绍了树形动态规划的基本概念、经典问题及其应用。树形dp是在树上进行动态规划,通过遍历获取信息来解决问题。文章列举了树的直径、重心、中心等经典问题的解决方法,并探讨了背包类树形dp的应用,如二叉苹果树和选课问题。此外,还提及了求树的最大独立集等相关题目。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

刚讲完状压,我们来讲讲树形dp

1.基本概念

树形 d p dp dp,顾名思义就是在树上做动态规划,通过若干次的遍历树,得到有用的信息,解决问题

树形 d p dp dp的枚举顺序类似线性 d p dp dp,也有两个方向:

①:叶→根:根的子节点传递有用的信息给根,由根得出最优解

②:根→叶:需要取出所有点作为根节点求值,最后根节点获得整棵树的信息

动态规划的顺序一般是后序遍历,即当子节点处理完成再处理父节点

树形 d p dp dp一般通过记忆化搜索实现

普通的树形 d p dp dp的时间复杂度基本都是 O ( n ) O(n) O(n),若有附加维则为 O ( n ∗ m ) O(n*m) O(nm)

2.经典问题

①树的直径

给你一颗 n n n个节点的有权有根树,根节点编号为1,找到一条最长的路径

最长链不一定经过根,所以不能单纯的从根贪心

要解决这个问题我们可以使用树形 d p dp dp

d [ x ] d[x] d[x]表示从节点 x x x出发走向以 x x x为根的子树,能够到达的最远节点的距离

d [ x ] = max ⁡ i ∈ s o n [ x ] d [ i ] + l e n g t h [ x ] [ i ] d[x]=\max_{i∈son[x]}d[i]+length[x][i] d[x]=maxison[x]d[i]+length[x][i]

答案就是 max ⁡ x ∈ 1... n max ⁡ i ∈ s o n [ x ] d [ x ] + d [ i ] + l e n g t h [ x ] [ i ] \max_{x∈1...n}\max_{i∈son[x]}d[x]+d[i]+length[x][i] maxx1...nmaxison[x]d[x]+d[i]+length[x][i]

void dp(int x) {
   
   
    v[x] = 1 ;
    for (int i = head[x]; i; i = e[i].nxt) {
   
   
        int y = e[i].to ;
        if (v[y]) continue ;
        dp(y) ;
        ans = max(ans, d[x] + d[y] + e[i].w) ;
        d[x] = max(d[x], d[y] + e[i].w) ;
    }
}

这个问题也可以通过两遍 b f s bfs bfs解决,作者在此就不多说了

②树的重心

树的重心的定义是这样的:若把树变成该点为根的有根树时,最大子树的节点数最小

这个问题也比较简单

随便选一个点作为根

s z [ x ] sz[x] sz[x]表示以 x x x为根的子树大小

不难发现 s z [ x ] = ∑ y ∈ s o n [ x ] s z [ y ] + 1 sz[x]=\sum_{y∈son[x]}sz[y]+1 sz[x]=yson[x]sz[y]+1

删除 x x x节点后最大连通块有多少节点? 子树最大有 m a x { s z [ y ] } max\{sz[y]\} max{ sz[y]}个,还有上面的它的祖先构成的树大小为 n − s z [ x ] n-sz[x] nsz[x] d p dp dp时顺便更新答案

void dfs(int rt, int fat) {
   
   
	sz[rt] = 1 ;
	rep(i, 0, siz(e[rt]) - 1) {
   
   
		int to = e[rt][i] ;
		if (to == fat) continue ;
		dfs(to, rt) ;
		sz[rt] += sz[to] ;
		dp[rt]= max(dp[rt], dp[to]) ;
	}
	dp[rt] = max(dp[rt], n - dp[rt]) ;
	ans = min(ans, dp
### Python 中树形动态规划(Tree DP)的实现方法 #### 基本概念 树形动态规划是一种基于树结构的动态规划技术,其核心在于通过递归的方式分解子问题并逐步构建解决方案。在树形DP中,通常会以节点为单位进行状态转移,最终汇总到根节点得出全局最优解。 #### 经典例题分析 以下是几个经典的树形DP问题及其对应的Python实现: --- #### 1. **二叉树的最大路径和** 该问题是典型的树形DP应用场景之一,目标是在一棵二叉树中找到一条路径使得路径上的节点值之和最大[^2]。 ##### 解决方案 可以通过自底向上的方式计算每棵子树的最大贡献值,并将其传递给父节点。 ```python class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def maxPathSum(root: TreeNode) -> int: res = float('-inf') # 记录全局最大路径和 def dfs(node): nonlocal res if not node: return 0 # 只有当子树的贡献大于零时才考虑加入当前路径 left_gain = max(dfs(node.left), 0) right_gain = max(dfs(node.right), 0) # 当前节点作为路径最高点的情况下的总收益 current_max_path = node.val + left_gain + right_gain res = max(res, current_max_path) # 返回当前节点对其父节点的最大贡献 return node.val + max(left_gain, right_gain) dfs(root) return res ``` 上述代码实现了对任意二叉树寻找最大路径和的功能,其中`dfs`函数负责递归遍历整个树结构,并更新全局变量`res`来记录最大路径和。 --- #### 2. **相邻字符不同的最长路径** 此问题要求在一棵树上找出满足特定条件的最长路径长度,即路径上的所有字符均不相同。 ##### 解决方案 可以采用DFS结合回溯的方法,在每次访问新节点时维护当前路径的状态集合。 ```python from collections import defaultdict def longestPath(parents, s): tree = defaultdict(list) for i in range(1, len(parents)): tree[parents[i]].append(i) result = 0 def dfs(node): nonlocal result first_longest, second_longest = 0, 0 char = s[node] for child in tree[node]: path_length = dfs(child) if s[child] != char: if path_length > first_longest: second_longest = first_longest first_longest = path_length elif path_length > second_longest: second_longest = path_length result = max(result, first_longest + second_longest + 1) return first_longest + 1 dfs(0) return result ``` 这段代码展示了如何处理带权图中的最优化问题,同时兼顾了约束条件——路径上的字符互异。 --- #### 提升树的应用对比 虽然提升树主要用于回归和分类任务,但它也体现了类似的分层决策机制。下面是一个简单的提升树回归模型示例[^3]: ```python import numpy as np class BoostingTree: def __init__(self, FX=[]): self.FX = FX def Regress(self, test_samples): """ 提升树的回归函数 """ base_tree = lambda x, v, f1, f2: (x < v and [f1] or [f2])[0] reg_efunc = lambda x: sum([base_tree(x, v, f1, f2) for v, f1, f2 in self.FX]) reg_func = np.frompyfunc(reg_efunc, 1, 1) return reg_func(test_samples) ``` 尽管此处展示的是完全不同的领域背景,但两者都依赖于层次化的数据拆分策略。 --- ### 总结 以上介绍了几种常见的树形动态规划实例以及其实现细节。这些例子涵盖了从基本操作到复杂逻辑的不同层面,能够帮助理解树形DP的本质特性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值