背包问题(四)-多重背包二进制优化(中等)

背包问题(四)-多重背包二进制优化(中等)



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

进阶:多重背包(苦难)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值