【python】动态规划算法学习:0-1背包问题 -牛客网HJ16 购物单

题目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,因此保证遍历顺序在一个维度上从小到大就好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值