1. 问题描述:
鲍勃喜欢玩电脑游戏,特别是战略游戏,但有时他找不到解决问题的方法,这让他很伤心。现在他有以下问题。他必须保护一座中世纪城市,这条城市的道路构成了一棵树。每个节点上的士兵可以观察到所有和这个点相连的边。他必须在节点上放置最少数量的士兵,以便他们可以观察到所有的边。你能帮助他吗?例如,下面的树:只需要放置 1 名士兵(在节点 1 处),就可观察到所有的边。
输入格式
输入包含多组测试数据,每组测试数据用以描述一棵树。对于每组测试数据,第一行包含整数 N,表示树的节点数目。接下来 N 行,每行按如下方法描述一个节点。节点编号:(子节点数目) 子节点 子节点 …节点编号从 0 到 N−1,每个节点的子节点数量均不超过 10,每个边在输入数据中只出现一次。
输出格式
对于每组测试数据,输出一个占据一行的结果,表示最少需要的士兵数。
数据范围
0 < N ≤ 1500
输入样例:
4
0:(1) 1
1:(2) 2 3
2:(0)
3:(0)
5
3:(3) 1 4 2
1:(1) 0
2:(0)
0:(0)
4:(0)
输出样例:
1
2
来源:https://www.acwing.com/problem/content/description/325/
2. 思路分析:
分析题目可以知道这道题目与285没有上司的舞会的题目是类似的,没有上司的舞会那道题目中树中的每条边至多存在一个点,求解的是快乐指数的最大值,这道题目中树中的每条边至少存在一个点才可以将所有的边覆盖,我们需要求解出至少需要多少个士兵能够将所有的边覆盖住;我们可以借助于没有上司的舞会的思路来求解,对于中的任意一棵子树,考虑局部的父节点与子节点的关系即可,对于树中的每一棵子树都使用同样的方法处理,所以使用递归解决即可,首先我们需要声明一个二维数组dp,其中dp[u][j]表示所有以u为根,并且状态为j的的选法的最小值,其中j = 0或者1,dp[u][0]表示不选择当前的根节点u的最小值,dp[u][1]表示选当前的根节点u的最小值,分别考虑dp[u][0]和dp[u][1]的情况即可,dp[u][0]表示没有选择当前的根节点由于每条边至少需要一个点,所以必须选择对应的子节点,对于dp[u][1]表示选择了当前的根节点,所以对于子节点可选也可以不选,我们只需要求解出当前选或者不选子节点的最小值即可,分情况讨论即可,由于我们需要覆盖每一条边,也即每条边上至少存在一个点,所以我们每一次在递归调用完之后需要将子节点递归得到的最小值累加到父节点上:
3. 代码如下:
python代码(出现超出内存的错误,后面发现可以通过之后再更新代码):
import sys
from typing import List
# 设置最大递归调用次数
sys.setrecursionlimit(100000)
class Solution:
def dfs(self, u: int, g: List[List[int]]):
# 可以发现只是用到了子节点的对应两个位置的值所以我们可以使用两个变量来替换, 这样可以省略一定的空间, 效果是一样的, t1表示不选当前的根节点u, t2表示选当前的根节点u
t1, t2 = 0, 1
for next in g[u]:
_t1, _t2 = self.dfs(next, g)
t1 += _t2
t2 += min(_t1, _t2)
return t1, t2
def process(self):
while True:
n = sys.stdin.readline().strip()
if not n: break
n = int(n)
# 节点编号从0开始
sta = [0] * n
# 由题目的输入可以看成是有向图, 所以只需要建一次边即可
g = [list() for i in range(n)]
for i in range(n):
t = sys.stdin.readline().strip().split(":")
a = int(t[0])
# 需要使用map函数将列表中的每一个元素转换为int类型
child = list(map(int, t[1].split(')')[1].strip().split()))
for ch in child:
g[a].append(ch)
sta[ch] = 1
root = 0
# 找到树的入口
while sta[root]: root += 1
t1, t2 = self.dfs(root, g)
print(min(t1, t2))
if __name__ == '__main__':
Solution().process()
import sys
from typing import List
sys.setrecursionlimit(10000)
class Solution:
def dfs(self, u: int, dp: List[List[int]], g: List[List[int]]):
# 因为每一次都是新建dp列表所以可以不用将dp[u][0]置为0
dp[u][0], dp[u][1] = 0, 1
for next in g[u]:
self.dfs(next, dp, g)
dp[u][0] += dp[next][1]
dp[u][1] += min(dp[next][0], dp[next][1])
def process(self):
while True:
n = sys.stdin.readline().strip()
if not n: break
n = int(n)
# 节点编号从0开始
dp = [[0] * 2 for i in range(n)]
sta = [0] * n
# 由题目的输入可以看成是有向图, 所以只需要建一次边即可
g = [list() for i in range(n)]
for i in range(n):
t = sys.stdin.readline().strip().split(":")
a = int(t[0])
# 需要使用map函数将列表中的每一个元素转换为int类型
child = list(map(int, t[1].split()[1:]))
for ch in child:
g[a].append(ch)
sta[ch] = 1
root = 0
# 找到树的入口
while sta[root]: root += 1
self.dfs(root, dp, g)
print(min(dp[root][0], dp[root][1]))
if __name__ == '__main__':
Solution().process()