这里写目录标题
题目HJ16 购物单
描述
王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 | 附件 |
---|---|
电脑 | 打印机,扫描仪 |
书柜 | 图书 |
书桌 | 台灯,文具 |
工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次。
每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。
王强查到了每件物品的价格(都是 10 元的整数倍),而他只有 N 元的预算。除此之外,他给每件物品规定了一个重要度,用整数 1 ~ 5 表示。他希望在花费不超过 N 元的前提下,使自己的满意度达到最大。
满意度是指所购买的每件物品的价格与重要度的乘积的总和,假设设第ii件物品的价格为v[i]v[i],重要度为w[i]w[i],共选中了kk件物品,编号依次为j1,j2,…,jkj1,j2,…,jk,则满意度为:v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk]v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk]。(其中 * 为乘号)
请你帮助王强计算可获得的最大的满意度。
输入描述:
输入的第 1 行,为两个正整数N,m,用一个空格隔开:
(其中 N ( N<32000 )表示总钱数, m (m <60 )为可购买的物品的个数。)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q
(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 ~ 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)
输出描述:
输出一个正整数,为张强可以获得的最大的满意度。
示例1
输入:
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出:
2200
问题理解
由于附件依赖于主件,附件不可能单独存在,也就是说附件的出现其实是主件的其它形态而已
一般地,我们考虑当金钱总额为j,而物件数量(主件)为i的情况。在这种情况下,能获得的最大满意度为:dp[i][j]
那么,dp[i][j] = max(不放置物品i,放置物品i)
而,
不放置物品i = dp[i-1][j]
放置物品i = 扣除物品i后的背包最大满意度 + 物品i价值 = dp[i-1][j - w[i]] + v[i]
但由题意,本题主件可能会以其它几种形式出现,即携带各种附件的组合后的花费及价值,因此放置物品i(主件i)的最大满意度实际上为:
放置物品i = max((dp[i-1][j - w[i]] + v[i]),(dp[i-1][j - w’[i]] + v’[i]),(dp[i-1][j - w’‘[i]] + v’'[i]),…),j - w[i] ≥ 0
也就是在所有满足约束的组合中取最优值
代码
本题已明确至多含有两种附件,因此可直接在条件中使用if判断来枚举搭配情况。
下面代码考虑了主件可能含有多种附件的情况,使用二进制法枚举出所有附件搭配情况进行dp,因此其实会有稍微的超时。
total_money,obj_num = map(int,input().split())
w_master_arr = []
w_slave_arr = []
v_master_arr = []
v_slave_arr = []
m_s_map = {}
master_line_map = {}
slave_info_arr = []
for i in range(obj_num):
w,v,master_line = map(int,input().split())
if(master_line == 0):
w_master_arr.append(w)
v_master_arr.append(w*v)
master_line_map[len(w_master_arr)] = i+1
else:
w_slave_arr.append(w)
v_slave_arr.append(w*v)
if(master_line not in m_s_map): m_s_map[master_line] = []
m_s_map[master_line].append(len(w_slave_arr))
dp_map = [[0]*(total_money+1)]
for i in range(1,len(w_master_arr)+1):
dp_map.append([0]*(total_money+1))
for j in range(1,total_money+1):
dp_candidates = []
dp_candidates.append(dp_map[i-1][j])
slave_objs = m_s_map[master_line_map[i]] if master_line_map[i] in m_s_map else []
slave_combinations = []
for k in range(1<<len(slave_objs)):
w_cnt = w_master_arr[i-1]
v_cnt = v_master_arr[i-1]
for l,slave_obj in enumerate(slave_objs):
if(k&(2**l) == 0): continue
w_cnt += w_slave_arr[slave_obj-1]
v_cnt += v_slave_arr[slave_obj-1]
if(w_cnt > j): continue
dp_candidates.append((dp_map[i-1][j-w_cnt]) + v_cnt)
dp_map[i][j] = max(dp_candidates)
print(dp_map[len(w_master_arr)][total_money])
将以上代码改为为本题做特定化判定,不超时:
total_money,obj_num = map(int,input().split())
w_master_arr = []
w_slave_arr = []
v_master_arr = []
v_slave_arr = []
m_s_map = {}
master_line_map = {}
slave_info_arr = []
for i in range(obj_num):
w,v,master_line = map(int,input().split())
if(master_line == 0):
w_master_arr.append(w)
v_master_arr.append(w*v)
master_line_map[len(w_master_arr)] = i+1
else:
w_slave_arr.append(w)
v_slave_arr.append(w*v)
if(master_line not in m_s_map): m_s_map[master_line] = []
m_s_map[master_line].append(len(w_slave_arr))
dp_map = []
for i in range(len(w_master_arr)+1): dp_map.append([0]*(total_money+1))
for j in range(1,total_money+1):
for i in range(1,len(w_master_arr)+1):
dp_map[i][j] = dp_map[i-1][j]
slave_objs = m_s_map[master_line_map[i]] if master_line_map[i] in m_s_map else []
w_cnt = w_master_arr[i-1]
if(w_cnt > j): continue
v_cnt = v_master_arr[i-1]
v_sum = (dp_map[i-1][j-w_cnt]) + v_cnt
if(v_sum > dp_map[i][j]): dp_map[i][j] = v_sum
if(master_line_map[i] not in m_s_map): continue
continue_flag = False
for slave_obj in m_s_map[master_line_map[i]]:
temp_w_cnt = w_cnt + w_slave_arr[slave_obj-1]
if(temp_w_cnt > j):
continue_flag = True
continue
temp_v_cnt = v_cnt + v_slave_arr[slave_obj-1]
v_sum = (dp_map[i-1][j-temp_w_cnt]) + temp_v_cnt
if(v_sum > dp_map[i][j]): dp_map[i][j] = v_sum
if(continue_flag or len(m_s_map[master_line_map[i]]) == 1): continue
w_cnt += w_slave_arr[m_s_map[master_line_map[i]][0] - 1] + w_slave_arr[m_s_map[master_line_map[i]][1] - 1]
if(w_cnt > j): continue
v_cnt += v_slave_arr[m_s_map[master_line_map[i]][0] - 1] + v_slave_arr[m_s_map[master_line_map[i]][1] - 1]
v_sum = (dp_map[i-1][j-w_cnt]) + v_cnt
if(v_sum > dp_map[i][j]): dp_map[i][j] = v_sum
print(dp_map[len(w_master_arr)][total_money])
另一个结论就是,dp矩阵的i,j遍历顺序是没影响的,因为高下标的dp依赖于低的dp,因此保证遍历顺序在一个维度上从小到大就好。