1. 问题描述:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。物品编号范围是 1…N。
数据范围
0 < N,V ≤ 1000
0 < vi,wi ≤ 1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
1 4
来源:https://www.acwing.com/problem/content/12/
2. 思路分析:
分析题目可以知道这道题目属于零一背包求具体方案的经典问题,所谓的具体方案其实是判断每一个当前的物品是否被选,也即怎么样决策的问题,动态规划求解具体的方案问题其实对应的是图论中的最短路问题;因为我们需要使用到下一个状态的值所以在求解具体方案的时候一般不使用状态压缩的写法,一般使用二维的动态规划写法,因为题目中设置了一个字典序最小的限制所以我们在求解零一背包问题的时候需要逆序遍历所有物品,这样可以确保选择物品的时候是从后面开始选择的,而且方便后面判断当前的物品是否被选择。其中dp[i][j]表示从第i个物品到最后一个物品中选且体积不超过j能够获得的最大价值,这样dp[1][V]就是在背包容量的前提下能够获得的最大价值。在求解字典序最小方案的过程中,我们从前往后遍历所有的物品,其中使用到的原则是对于当前的物品能选则选,这样在从前往后遍历的过程中字典序一定是最小的。
3. 代码如下:
if __name__ == '__main__':
n, V = map(int, input().split())
# 先存储0这个元素后面可以保证下标从1开始
v, w = [0], [0]
# 先存储物品的体积和价值这样方面后面倒着递推
for i in range(n):
_v, _w = map(int, input().split())
v.append(_v)
w.append(_w)
dp = [[0] * (V + 1) for i in range(n + 2)]
# 从后往前递推, 与之前不一样的是其实变换的只是下标
for i in range(n, 0, -1):
for j in range(V + 1):
dp[i][j] = dp[i + 1][j]
if j - v[i] >= 0:
dp[i][j] = max(dp[i][j], dp[i + 1][j - v[i]] + w[i])
j = V
# 从前往后判断是否选择当前的物品
for i in range(1, n + 1):
if j - v[i] >= 0 and dp[i][j] == dp[i + 1][j - v[i]] + w[i]:
print(i, end=" ")
j -= v[i]
class Solution:
def process(self):
n, V = map(int, input().split())
v, w = list(), list()
for i in range(n):
a, b = map(int, input().split())
v.append(a)
w.append(b)
dp = [[0] * (V + 10) for i in range(n + 10)]
for i in range(n - 1, -1, -1):
for j in range(V + 1):
dp[i][j] = dp[i + 1][j]
if j - v[i] >= 0:
dp[i][j] = max(dp[i][j], dp[i + 1][j - v[i]] + w[i])
# 从前往后遍历
j = V
for i in range(n):
if j - v[i] >= 0 and dp[i + 1][j - v[i]] + w[i] == dp[i][j]:
j -= v[i]
print(i + 1, end=" ")
if __name__ == "__main__":
Solution().process()