之前简单介绍了一下0/1背包问题,详情可见动态规划——0/1背包问题。
但是后来在做华为机考的另一道题,发现这道题需要用有依赖的背包问题来解决,接下来我为大家简单介绍一下这道题以及如何用有依赖的背包问题来解决。
一、题目描述
王强今天很开心,公司发给N元的年终奖。王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 | 附件 |
电脑 | 打印机,扫描仪 |
书柜 | 图书 |
书桌 | 台灯,文具 |
工作椅 | 无 |
输入描述
输入的第 1 行,为两个正整数,用一个空格隔开:N m
输出描述
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值( <200000 )
示例输入
1000 5 800 2 0 400 5 1 300 5 1 400 3 0 500 2 0
示例输出
2200
二、分组背包问题
我们可以看到,这道题目里面不同于0/1背包问题,它有个主件与附件的依赖问题(买主件就必须买附件),所以这就用到我们的分组背包问题。
分组背包问题首先要做的就是分组,分好组之后,若组内的选择大于一种,则只能在组内选择其中一种,或者全部不选,然后再按照0/1背包问题去构建二维数组即可。
举个简单的例子:
假设有五件物品:1,2,3,4,5,分组情况为(1,2),(3,4),(5),且问题描述与0/1背包问题相同。
我们就可以画出二维数组如下:
1 | 2 | 3 | ... | |
1,2 | B(1,1) | B(1,2) | B(1,3) | ... |
3,4 | B(2,1) | B(2,2) | B(2,3) | ... |
5 | B(3,1) | B(3,2) | B(3,3) | ... |
我们以计算B(2,3)为例:
在将第二组考虑进来之后,我们只能选择要不全部不选,要不只选择物品3,要不只选择物品4,所以我们可以得到下面的公式:
当然,物品3或者4可选的前提条件是 j >= w[3] 或 j >= w[4]。若不满足该条件则不加进来作比较。
三、题解
根据题目描述的主件与附件的关系,我们可以分组如下:
第一组:[ ] 空列表
第二组:[ (1, ), (1, 2 ), (1, 3 ), (1, 2, 3)] 主件1与其附件的选择
第三组:[ ( 4,), ] 物品4
第四组:[ ( 5,), ] 物品5
故我们可以画出二维数组如下:
0 | 10 | 20 | ... | 1000 | |
[ ] | |||||
[ (1, ), (1, 2 ), (1, 3 ), (1, 2, 3)] | B(2,j) | ||||
[ ( 4,), ] | |||||
[ ( 5,), ] |
同样以B(2, j)为例,我们看一下求解公式:
四、代码实现
接下来我们用代码实现上述算法:
首先为了构建分组,我们这里用到了python中的chain与combinations模块
from itertools import chain,combinations def powerset(iterable): # powerset([1,2,3]) ——> [(),(1,),(2,),(3,),(1,2,),(1,3),(2,3),(1,2,3)] s = list(iterable) return list(chain.from_iterable(combinations(s,r) for r in range(len(s)+1)))
记下来写主代码:
初始化:
from collections import defaultdict capacity,num = list(map(int,input().split())) p = [0] # 价格 w = [0] # 重要程度 c = [0] # 从属关系 d = defaultdict(list) # d[key] 默认为[] for i in range(num): price,weight,cate = list(map(int,input().split())) p += [price] w += [weight] c += [cate]
分组:
for i in range(1,num+1): if c[i] == 0: d[i] =[] for i in range(1,num+1): if c[i] != 0: d[c[i]].append(i) groups = [[0]] # 分组 for k,v in d.items(): l = powerset(v) l = list(map(lambda x: x+(k,),l)) groups.append(l)
动态规划:
table = [[0 for column in range(int(capacity//10)+1)] for row in range(len(groups))] for i in range(1,len(groups)): for j in range(1,int(capacity//10)+1): competition = [table[i-1][j]] for group in groups[i]: sum_price = 0 sum_weight_price = 0 for item in group: sum_price += p[item] # 把每个item价格加起来 sum_weight_price+=p[item]*w[item] if j*10 >= sum_price: competition += [table[i-1][j-int(sum_price/10)]+sum_weight_price] #加上一个group的对应值 table[i][j] = max(competition) print(table[-1][-1])