什么是树形DP
顾名思义,是指将DP建立在树状结构的基础上。
例题:结点选择
问题描述
有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?
输入格式
第一行包含一个整数 n 。
接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。
接下来一共 n-1 行,每行描述树上的一条边。
输出格式
输出一个整数,代表选出的点的权值和的最大值。
样例输入
5
1 2 3 4 5
1 2
1 3
2 4
2 5
样例输出
12
思路:找出选树中的哪些节点可以得到最大的权值和。
从上往下找,每选一个节点都要考虑其相邻的子节点是否选中,较为麻烦,因此我们从叶子结点开始向上找。dp[i][j] 当i为底部叶子结点时,dp[i][1]表示选中i结点时的权值——k,dp[i][0]表示不选i结点时的权值——0。从下往上推,dp[i][j]当i不为叶子结点时,dp[i][1]表示选中i时,i及其子树的最大权值;dp[i][0]表示不选i时,i及其子树的最大权值。
因此DP的边界即为叶子结点的状态:
dp[i][0] = 0;
dp[i][1] = k(权值);
状态转移方程:
dp[i][1] = value(i)【i点权值】 + ∑dp[j][0] 【j为i的子树根节点】
dp[i][0] = max(∑dp[j][1] ,∑dp[j][0])
当父节点选中时,相邻子节点必然不能选中;而当父节点未选中时,当前节点的最大权值和则为相邻子节点选中和未选中两种情况中的更大值(不一定必须选中)
题目中的解即为max(dp[1][0],dp[1][1])
因为我们需要知道一个节点的子节点有哪些,同时也要知道一个节点的父节点有哪些(即一个节点的临近结点),因此我们采用字典来存储输入的树。(类似存储无向图)
dict.setdefault(key,[]).append(value) //创建value为列表的字典
当key为root的值时,可以将所有以root为父节点的子节点append入列表中。
然而如何,遍历求得dp数组的值呢?不能像普通dp一样直接for循环填充。我们使用DFS来对所有结点进行dp状态的求解。
DFS(root) 从根节点开始对邻接节点进行深搜,直至所有的结点都被访问过
def dfs_Tree(u):#输入结点u,输出选择结点u和不选择结点u时候的最大权值
flag[u] = 1
for i in range(len(tree[u])): #遍历u的邻接节点个数
if flag[tree[u][i]] == 0: #u的i节点没有遍历过,则遍历
dfs_Tree(tree[u][i]) #对u的邻接结点i进行dfs
#如果选择结点u的话,其邻接结点一定不可选
dp[u][1] += dp[tree[u][i]][0]
#没有选择结点u的话,其邻接结点可选可不选,所以取这两种情况的最大值
dp[u][0] += max(dp[tree[u][i]][1],dp[tree[u][i]][0])
n = int(input())#结点个数
node_list = list(map(int,input().split( ))) #n个结点的权值
tree = {} #存无向图
for i in range(1,n):
root,child = map(int,input().split( ))
tree.setdefault(root,[]).append(child)
tree.setdefault(child,[]).append(root)
flag =[0 for i in range(n+1)] #置每个结点被访问的状态为0:未访问
dp = [[0]*2 for i in range(n+1)]
for i in range(1,n+1): #初始化假设每个节点都是叶子结点;每个节点都被选中=自己的权值
dp[i][1] = node_list[i-1]
dfs_Tree(1)
#最大权值即为max(dp[1][0], dp[1][1])
print(max(dp[1][0],dp[1][1]))