今日复习内容:还是动态规划
今天我复习的是树形DP,也就是自上而下树形动态规划,这是一种解决背包问题的方法,通常用于处理涉及树状结构的动态规划问题。在这种方法中,问题被建模成一棵树,其中节点表示状态,边表示状态之间的转移。接下来是我自己的理解。
1.问题描述
自上而下树形动态规划常用于解决具有树状结构的问题,例如树上的动态规划问题或者涉及到递归结构的问题。在这类问题中,常需要在节点上进行一些操作,并根据子节点的结果进行状态转移。
2.状态定义
定义合适的状态是自上而下树形动态规划的关键。每个节点可以表示一个状态,状态中包括当前节点的信息以及从该节点出发的边。
3.递归函数
使用递归函数来模拟树的遍历过程。递归函数通常接收当前节点的状态作为参数,然后递归地调用子节点的状态。
4.记忆化搜索
为例避免重复计算,使用记忆化搜索来保存已经计算过的状态。在每个节点处记录计算结果,下次遇到相同状态时直接返回已保存的结果。
5.示例问题
以背包问题为例,树形结构可以表示物品之间的依赖关系。每个节点表示选择或不选择当前物品,边表示物品间的依赖关系。递归函数考虑当前节点的选择,并递归调用子节点的状态。
6.优点和试用场景
自上而下树形动态规划在解决树状结构问题时具有灵活性和直观性。它可以更自然地反映问题的本质,并减少不必要的计算。适用于问题可以自然地建模成树状结构的情况。
7.注意事项
需要小心处理边界条件,确保递归函数能够正确终止。同时,确保状态转移的过程符合问题的实际逻辑。
接下来,我举个例子,是我在学的过程中找的一个题。
问题描述:给定一棵二叉树,每个节点包含一个整数值。找到路径和的最大值。路径被定义为从某个起始节点到树中的任意节点的任何节点序列,该路径必须至少包含一个节点,且不需要经过根节点。
示例树:
1
/ \
2 3
/ \
4 5
解题思路:
1.状态定义
在这个问题中,树中的每个节点表示一个状态。我们需要考虑包含该节点的最大路径和。我们还可以考虑从该节点开始并通过其子节点向下的最大路径和。
2.递归
我们可以定义一个递归函数maxPathSum(node),用于计算包含给定节点的最大路径和。该函数将计算左子节点和右子节点的最大路径和,并将它们加到节点的值中。我们还需要考虑仅包含节点本身的路径和的情况。
3.记忆化搜索
我们可以使用记忆化搜索来存储已经计算过的路径结果,这有助于避免多次计算相同的路径。
4.基本情况
当函数遇到叶子节点(没有子节点的节点)时,即为基本情况。在这种情况下,函数返回节点本身的值。
def maxPathSum(node):
if node is None:
return 0
# 基本情况,叶子节点
if node.left is None and node.right is None:
return node.val
# 计算左右子树的最大路径和
left_sum = maxPathSum(node.left)
right_sum = maxPathSum(node.right)
# 将当前节点值加到左右子树的最大路径和中
max_sum = max(node.val,node.val + left_sum,node.val + right_sum)
#如果必要,更新全局最大路径和
global max_path_sum
max_path_sum = max(max_sum,max_path_sum)
#返回包括当前节点的最大路径和
return max_sum
解释:
对于给定的示例树,最大路径和为12,路径为4 --> 2 --> 1 --> 3。maxPathSum函数从根节点1开始,计算其左子节点2和右子节点3的最大路径和,最后,它将更新全局最大路径和为12。
好了,基本思想就是这样,接下来来做一个题。
题目描述:
给一棵有根树,每个点都有一 个权值ai,要选择一些点使得权值和最大,且一个点若被选择,其儿子不能被选择,问最大值。
思路:这个题只是多了一个限制。
参考答案:
from collections import defaultdict
n = int(input())
val = [0] + [int(x) for x in input().split()]
edges = defaultdict(list)
f = [[0, val[i]] for i in range(n + 1)]
def add_edge(from_node, to_node):
edges[from_node].append(to_node)
def dfs(u, fa):
for v in edges[u]:
if v == fa:
continue
dfs(v, u)
f[u][0] += max(f[v][0], f[v][1])
f[u][1] += f[v][0]
for _ in range(n - 1):
u, v = map(int, input().split())
add_edge(u, v)
add_edge(v, u)
dfs(1, 0)
print(max(f[1][0], f[1][1]))
我来记录一下这个代码我是怎么照着写出来的。(这是我个人观点啊)
这段代码实现了一个解决树形动态规划问题的算法,其目标是选择一些节点使得权值和最大,且如果选择了某个节点,其子节点就不能被选择。以下是我对代码的解释:
1.输入处理
n = int(input()):输入树的节点数目
val = [0] + [int(x) for x in input().split()]:输入每个节点的权值,其中val[i]表示第i个节点的权值。
2.构建树结构
edges = defaultdict(list):使用字典来存储树的边,键为节点编号,值为与该节点相邻的节点列表
def add_edge(from_node,to_node):edge[from_node].append(to_node):
定义一个函数add_edge,用于向树中添加边,将from_node到to_node的边添加到边列表中。
3.动态规划过程
f = [[0,val[i]] for i in range(n + 1)]:f[i]表示以节点i为根节点的子树的最大权值和,其中f[i][0]表示不选取节点i时的最大权值和。
def dfs(u,fa):定义了一个深度优先搜索函数dfs,用于计算以节点u为根节点的子树中的最大权值和,fa表示节点u的父节点。
for v in edges[u]:遍历节点u的相邻节点
if v == fa:continue:如果相邻节点v是u的父节点,则跳过
dfs(v,u):递归地调用dfs函数,计算以v为根节点的子树中的最大权值和
f[u][0] += max(f[v][0],f[v][1]):更新不选取节点u的最大权值和,选择节点u时,其子节点不能被选择,所以累加最大的子节点权值和。
f[u][1] += f[v][0]:更新选取节点u时的最大权值和,选择节点u时,其子节点可以不被选择,所以累加子节点不选取时的最大权值和。
4.建立树结构
for _ in range(n - 1):u,v = map(int,input().split()):通过n - 1条边的输入构建树结构,每条边连接两个节点u和v。
add_edge(u,v)和add_edge(v,u):分别将边(u,v)和(v,u)添加到树的边列表中。
5.计算结果
dfs(1,0):从根节点开始进行深度优先搜索,计算以根节点为根的子树中的最大权值和。
print(max(f[1][0],f[1][1])):输出根节点的最大权值和,即选择根节点和不选择根节点两种情况中的最大值。
这段代码利用了动态规划,在遍历树的过程中,逐步计算每个节点的最大权值和,最终得到整棵树的最大权值和。
OK,这篇就到这里了,下一篇继续!