动态规划——分组背包问题

之前简单介绍了一下0/1背包问题,详情可见动态规划——0/1背包问题

但是后来在做华为机考的另一道题,发现这道题需要用有依赖的背包问题来解决,接下来我为大家简单介绍一下这道题以及如何用有依赖的背包问题来解决。

一、题目描述

王强今天很开心,公司发给N元的年终奖。王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:

主件 附件
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。王强想买的东西很多,为了不超出预算,他把每件物品规定了一个重要度,分为 5 等:用整数 1 ~ 5 表示,第 5 等最重要。他还从因特网上查到了每件物品的价格(都是 10 元的整数倍)。他希望在不超过 N 元(可以等于 N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
    设第 j 件物品的价格为 v[j] ,重要度为 w[j] ,共选中了 k 件物品,编号依次为 j1 , j2 ,……, jk ,则所求的总和为:
v[j1 ]*w[j1 ]+v[j2 ]*w[j2 ]+ … +v[jk ]*w[jk ] 。(其中 * 为乘号)
    请你帮助王强设计一个满足要求的购物单。

输入描述

输入的第 1 行,为两个正整数,用一个空格隔开:N m

(其中 N ( <32000 )表示总钱数, m ( <60 )为希望购买物品的个数。)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q
(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 ~ 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)

 输出描述

 输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值( <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])
原文地址:https://www.cnblogs.com/sunny0824/p/13509526.html