题解 | #购物单#
购物单
https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4
题目说了商品价格为10的倍数,动态规划数组dp的行数=商品数目,dp列数=总钱数,为了减少dp数组的列维度,将总钱数m整除10(dp数组列维度便缩小10倍),总钱数小了10倍再把每件商品价格也缩小10倍,购买方案就会和原来不变。注意:每件商品满意度在存储时仍记录原价的计算结果,即商品满意度=商品原价*商品重要程度
一、动态规划数组dp[i][j]:
含义:总钱数为j,遍历到第i件商品时,所能买到的最大满意度值
维度:N行m列,N为商品总数目,m为总钱数
状态转移:dp[i][j] = max(dp[i-1][j], dp[i-1][j-Price[i]]+Weight[i]),当总钱数j>=商品i价格Price[i]时
以遍历到上一件商品i-1为起点,dp[i-1][j]没有选择购入接下来的产品i,总钱数j保留,获取记录的满意度值dp[i-1][j];dp[i-1][j-Price[i]]选择购入接下来的i产品,总钱数j减去商品价格,对获取到的满意度记录值dp[i-1][j-Price[i]] 再加上商品i的满意度Weight[i]
优化数组dp
1、二维缩小为一维:二维dp数组第i行总是用到第i-1行数据,可以每次只保留上一行数据留给下次遍历使用,由原来的二维dp[i][j]变成只记录一行(每次遍历前的上一行i-1)数据的一维dp[j]
2、对总钱数j进行倒序遍历:优化后的一维dp只保留上次的行记录值,新的状态转移中 dp[j] = max(dp[j], dp[j-Pi]+Wi),索引 j的值总要依赖更小索引 j-Pi的记录值。当 j从大到小倒序遍历时,由于优化后的一维dp只保留上次的行记录值,比索引 j小的其他记录值还是上次的行记录值;但是当 j从小到大正序遍历时,会不断更新一维dp记录值,随着索引 j不断增大,用到的更小索引 j-Pi已经不是上次的行记录值,而是刚刚才更新过的新值(上次的行记录值已被覆盖)
二、更新dp数组的条件
当前总钱数 j 大于等于待购买商品总价时,更新dp数组
附件不能单独购买,所以遍历时只看主件。一个主件没有附件时,只考虑max(不买,买主件),有附件时:
1、有1个附件:当主件+附件总价格 <= 总钱数j,dp[j] = max(不买,买主件,买主件+附件)
2、有2个附件:
- 当主件+附件1总价格 <= 总钱数j,dp[j] = max(不买,买主件,买主件+附件1);
- 当主件+附件2总价格 <= 总钱数j,dp[j] = max(不买,买主件,买主件+附件2);
- 当主件+附件1总价格+附件2总价格 <= 总钱数j,dp[j] = max(不买,买主件,买主件+附件1,买主件+附件2,买主件+附件1+附件2)
class Good:
def __init__(self, id, price, weight, belong) -> None:
self.id = id # 商品编号
self.price = price # 商品价格
self.weight = weight # 商品满意度:价格*重要度
self.belong = belong # 商品所属
self.attach = [] # 商品附件
def add_attach(self, good):
self.attach.append(good)
while True:
try:
m, N = list(map(int, input().split(" ")))
# 1、dp数组长度为总钱数,将商品价格与总钱数同比缩小10倍,缩小dp长度
# 2、dp由2维减为1维度,实际只记录:遍历原2维数组时当前行的上一行
m //= 10
dp = [0 for _ in range(m + 1)] # 总钱数为0,1,2...m
goods = [None, ] # 跳过索引0,从索引1开始
for i in range(1, N + 1):
p, w, b = list(map(int, input().split(" ")))
good = Good(i, p // 10, w * p, b) # 题目说商品价格为10的整数倍,这里对商品单价//10
goods.append(good)
# ------------- 遍历商品,将所有附件添加到对应主件的attach中
for good in goods[1:]:
if good.belong != 0: # 该商品为附件,good.belong为其主件的编号
goods[good.belong].add_attach(good)
# ------------- 遍历商品中的主件,更新dp数组
for good in goods[1:]:
if good.belong == 0: # 遍历主件
for j in reversed(range(m + 1)): # 对一维dp的索引j进行倒序遍历,避免上次记录值被覆盖
if j >= good.price:
# 为dp[j]赋初值:max(不买, 主)
dp[j] = max(dp[j], dp[j - good.price] + good.weight)
# 根据主件拥有附件的个数,再更新dp[j]记录值
if len(good.attach) == 0:
continue
elif len(good.attach) == 1:
attach1 = good.attach[0]
# max(不买, 主, 主+附1)
if j >= good.price + attach1.price:
dp[j] = max(
dp[j],
dp[j-good.price-attach1.price] + good.weight + attach1.weight,
)
elif len(good.attach) == 2:
attach1 = good.attach[0]
attach2 = good.attach[1]
# a = max(不买, 主, 主+附1)
if (j >= good.price + attach1.price):
dp[j] = max(
dp[j],
dp[j-good.price-attach1.price] + good.weight + attach1.weight,
)
# b = max(a, 主+附2)
if (j >= good.price + attach2.price):
dp[j] = max(
dp[j],
dp[j-good.price-attach2.price] + good.weight + attach2.weight,
)
# max(b, 主+附1+附2)
if (j >= good.price + attach1.price + attach2.price):
dp[j] = max(
dp[j],
dp[j-good.price-attach1.price-attach2.price] + good.weight + \
attach1.weight + attach2.weight,
)
else:
break
print(dp[m])
except:
break
#动态规划#高质量题: 1~40:HJ16,HJ22,HJ24,HJ26,HJ27,HJ28,HJ35,HJ37,HJ39; 40~80:HJ41,HJ42,HJ43,HJ44,HJ48,HJ50,HJ52,HJ53,HJ57,HJ61,HJ63,HJ64,HJ70,HJ71,HJ74,HJ77; 80~108:HJ82,HJ85,HJ88,HJ89,HJ93,HJ95,HJ98,HJ103,HJ107
查看11道真题和解析
