背包问题(四)-多重背包二进制优化(中等)
1. 题目
- 问题描述:有n件物品和容量为m的背包,给出i件物品的重量以及价值value,还有数量number,求解让装入背包的物品重量不超过背包容量W,且价值V最大 。
- 特点 :它与完全背包有类似点,特点是每个物品都有了一定的数量。
2. 分析
2.1 状态表示
在此之前我们已经完成了多重背包的基础解法,这种解法的其实在状态转移方程上已经没有优化空间。但是对于数据本身我们可以做改造。
一般还是使用dp数组来计算动态规划问题,从以下两个方面对动态规划问题进行表示
- 集合
- v集合,原先是物品的价值,现在我们用来存放经过2进制“折叠”的数据
- w集合,原先是物品的重量,现在我们用来存放经过2进制“折叠”的数据
- 从前i个物品里面选取总重量<=j的所有物品的选法,与完全背包的区别在于,每一种物品是有个数限制的,不能无限选择
- 因此此处需要多一个num集合:每个物品的数量
- 属性
- max
- min
- count
本题属性是属于求最大价值,为max
2.2 优化算法
我们上述说到的v、w集合的变化,其实此处其实对元数据进行了改装,是基于2的次方跳选k的方法:
2的次方跳选,说白了就是我们不用一个个+1的方式去寻找这个k的具体值。
说起来很简单,即每一个正整数,其实都可以用【2的次方】+【一个常数】来表示
如:
# 表示67
67 = 64 + 3 = 2^6 + 3
# 表示127
127 = 64 + 63 = 2^6 + 63
# 表示131
131 = 128 + 3 = 2^7 + 3
因此我们就可以对2的次幂进行选择,即选不选这个幂的问题,令当前要表示的数为p,则
p = 2^k + c
这时候遍历k,则可以以很快的速度定位到k的具体位置,而选择需不需要用到这个k。
也就是说假设我们第i个物品需要选择t个,则有t的范围是:
2^k < t < 2^(k+1)
而且
c = t - 2^k.
由此可见,对于每一个物品来说,c是常数,我们只需要确定t的取值即可
因此此时的问题可以转换为,选择放不放当前这一次2的次方个数的物品的问题
实际上就是一个01背包的问题了,我们可以得到得状态转移方程:
dp[t] = max(dp[t], dp[t-w[i]] + v[i])
# t实际上代表的是2的幂
# v,w集合存的实际上是经过计算后乘上了2次幂的数据
3. 实现(go实现)
根据上面的状态转移方程我们可以得到多重背包的解法:
// 《把多重背包转换成01背包,二进制优化法》
// 对元数据本身进行操作
// 我们把k分解成,【二的次方的和】+【剩余常数】
// 改装后的元数据,其实就只剩下
func pakMultipleBin(totalNum, totalWeight int, v, w, num []int) int {
count := 1 // 表示数组的长度
var tv, tw [COL]int
for i:=0; i<totalNum; i++ {
a, b, s := v[i], w[i], num[i]
// 把当前的s进行分解,看看这个取值k可以到多少范围
k := 1
for k <= s {
tv[count] = a * k
tw[count] = b * k
s -= k
k <<= 1
count++
}
// 这个s没完全被分解完的部分,即上述的常数c
if s != 0 {
tv[count] = a * k
tw[count] = b * k
count++
}
}
// 至此,我们的数据已经归整完毕
// tv,tw:存放的是,当2次幂个物品被放进来的时候,的价值,下标为count,这时候只需要进行01背包运算即可
var dp [COL]int
for i:=1; i<=count; i++ {
for j:=totalWeight; j>=tw[i]; j-- {
input := dp[j-tw[i]]+tv[i]
notput := dp[j]
dp[j] = max(input, notput)
}
}
return dp[totalWeight]
}
5. 测试
我们给出01背包的测试数据
{
"things_num": 10,
"items": [{
"value": 7,
"number": 5,
"weight": 1
}, {
"value": 13,
"number": 2,
"weight": 4
}, {
"value": 18,
"number": 1,
"weight": 1
}, {
"value": 5,
"number": 1,
"weight": 7
}, {
"value": 20,
"number": 3,
"weight": 10
}, {
"value": 19,
"number": 1,
"weight": 9
}, {
"value": 6,
"number": 5,
"weight": 10
}, {
"value": 12,
"number": 4,
"weight": 9
}, {
"value": 8,
"number": 1,
"weight": 3
}, {
"value": 10,
"number": 4,
"weight": 6
}],
"total_weight": 37
}
输出:
92