基本思想:
动态规划与分治法类似,其基本思想也是将待求问题,分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。而与分治法不同的是,对子问题之间重叠的部分,分治法会重复地求解这些重叠的部分,而动态规划只会对它们求解一次,然后用表格保存这些解,如此,就可以很大程度地避免重复计算。
解题思路:
1. 存储输入数据
显然输入的是二维的数据,应该用二维数组来存储。当然,还有背包容量的存储
# 读取输入
capacity = int(input())
items = []
for i in range(5):
line = input().strip()
if line:
parts = list(map(int, line.split()))
# line.split() 按空格分隔字符串得到字符串列表
# map(int, ...):将每个字符串转换为整数
# list(...):将结果转换为列表 从而实现双重列表
if len(parts) == 3:
num, weight, value = parts
items.append((num, weight, value))
n = len(items) # 物体的总数
# 可有可无
if n == 0:
print("没有数据输入")
exit()
这里因为只有5组输入,所以使用了for循环进行输入,而实际上这样代码的适用性一般,以下是改进。改进后就可以实现随机组数据的输入。
# 读取输入
capacity = int(input())
items = []
while True:
try:
line = input().strip()
if line:
parts = list(map(int, line.split()))
if len(parts) == 3:
num, weight, value = parts
items.append((num, weight, value)) # 格式:(编号,重量,价值)
except EOFError:
break
n = len(items) # 物体的总数
# 可有可无
if n == 0:
print("没有输入数据")
exit()
# line.split() 按空格分隔字符串得到字符串列表
# map(int, ...):将每个字符串转换为整数
# list(...):将结果转换为列表 从而实现双重列表
2.初始化动态规划表
其实就是创建一个二维数组,初值都先赋为0
# 初始化动态规划表
dp = [[0] * (capacity + 1) for _ in range(n + 1)] #创建一个二维数组 dp,维度为 (n+1) x (capacity+1),初始值全为0
3.填充动态规划表
重难点!!也是关键点
1. 动态规划表的定义
dp[i][w] 表示:考虑前i个物品,在背包容量为w时能获得的最大价值。
2. 填表的基本原理
表格填充遵循以下决策逻辑:
• 不选当前物品:价值继承自上一行同列的值
• 选当前物品:当前物品价值 + 剩余容量的最优解
3. 具体填表过程解析
for i in range(1, n + 1): # 逐个考虑物品
weight, value, _ = items[i-1] # 获取当前物品属性
for w in range(capacity + 1): # 遍历所有可能容量
if weight > w:
dp[i][w] = dp[i-1][w] # 情况1:放不下
else:
# 情况2:选择是否放入
dp[i][w] = max(dp[i-1][w], dp[i-1][w - weight] + value)
深度解析:
1. 情况1很容易理解,放不下,那就顺延之前的方案
2.而情况2,选择是否放入,这一部分我们利用例子来说明
理解误区:
这里的动态规划表事实上还是将每种可能性都有计算
所以这里的dp[i-1][w-weight]+value其实是已知的,这样才能计算出数据与不拿的情况相比较
从而取其最大值
4.回溯选中的物品
注意:是从后向前选择,所以后面要反转得到正确顺序
selected = [] # 用来存储选中的物品的编号
current_weight = capacity
for i in range(n, 0, -1):
if dp[i][current_weight] > dp[i-1][current_weight]: # 二者不相等,说明物品有放入背包
item = items[i-1]
selected.append(item[0])
current_weight -= item[1] # 减去已选中物体的重量
着重关注第一和第二点(虽然比喻有点抽象)
5.输出结果
# 反转得到正确顺序,因为之前是倒取的
selected.reverse()
# 输出结果
#将selected转换为字符串后输出,然后用join连接
print(' '.join(map(str, selected)))
汇总实现
1. 已知输入数据量版
# 读取输入
capacity = int(input())
items = []
for i in range(5):
line = input().strip()
if line:
parts = list(map(int, line.split()))
# line.split() 按空格分隔字符串得到字符串列表
# map(int, ...):将每个字符串转换为整数
# list(...):将结果转换为列表 从而实现双重列表
if len(parts) == 3:
num, weight, value = parts
items.append((num, weight, value))
n = len(items)
# 可有可无
if n == 0:
print("")
exit()
# 初始化动态规划表
dp = [[0] * (capacity + 1) for _ in range(n + 1)] #创建一个二维数组 dp,维度为 (n+1) x (capacity+1),初始值全为0
for i in range(1, n + 1):
num, weight, value = items[i-1]
for w in range(capacity + 1):
if weight > w: # 如果太重,就不放入
dp[i][w] = dp[i-1][w]
else: # 如果可以放入,那就判断是选择放还是不放
dp[i][w] = max(dp[i-1][w], dp[i-1][w - weight] + value)
# 回溯选中的物品 注意:是从后向前选择,所以后面要反转得到正确顺序
selected = [] # 用来存储选中的物品的编号
current_weight = capacity
for i in range(n, 0, -1):
if dp[i][current_weight] > dp[i-1][current_weight]: # 二者不相等,说明物品有放入背包
item = items[i-1]
selected.append(item[0])
current_weight -= item[1] # 减去已选中物体的重量
# 反转得到正确顺序
selected.reverse()
# 输出结果
#将selected转换为字符串后输出,然后用join连接
print(' '.join(map(str, selected)))
2.泛用版
# 读取输入
capacity = int(input())
items = []
while True:
try:
line = input().strip()
if line:
parts = list(map(int, line.split()))
if len(parts) == 3:
num, weight, value = parts
items.append((weight, value, num)) # 统一使用 (重量, 价值, 编号) 结构
except EOFError:
break
n = len(items)
if n == 0:
print("")
exit()
# 初始化动态规划表
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
# 填充动态规划表
for i in range(1, n + 1):
weight, value, num = items[i-1] # 现在解包顺序正确
for w in range(capacity + 1):
if weight > w:
dp[i][w] = dp[i-1][w]
else:
dp[i][w] = max(dp[i-1][w], dp[i-1][w - weight] + value)
# 回溯选中的物品
selected = []
current_weight = capacity
for i in range(n, 0, -1):
if dp[i][current_weight] > dp[i-1][current_weight]:
weight, value, num = items[i-1] # 正确解包
selected.append(num) # 添加编号
current_weight -= weight # 减去重量
# 反转得到正确顺序
selected.reverse()
# 输出结果
print(' '.join(map(str, selected)))