0-1背包问题解析

0-1背包问题是指每一种物品都只有一件,可以选择放或者不放。现在假设有n件物品,背包承重为m。

对于这种问题,我们可以采用一个二维数组去解决:f[i][j],其中i代表加入背包的是前i件物品,j表示背包的承重,weight[i]代表第i件物品的重量,f[i][j]表示当前状态下能放进背包里面的物品的最大总价值。那么,f[n][m]就是我们的最终结果了。

采用动态规划,必须要知道初始状态和状态转移方程。初始状态很容易就能知道,那么状态转移方程如何求呢?对于一件物品,我们有放进或者不放进背包两种选择:

  (1)假如我们放进背包,f[i][j] = f[i - 1][j - weight[i]] + value[i],这里的f[i - 1][j - weight[i]] + value[i]应该这么理解:在没放这件物品之前的状态值加上要放进去这件物品的价值。而对于f[i - 1][j - weight[i]]这部分,i - 1很容易理解,关键是 j - weight[i]这里,我们要明白:要把这件物品放进背包,就得在背包里面预留这一部分空间,也就是在预留这部分空间下的状态时,背包所能达到的最大价值。

  (2)假如我们不放进背包,f[i][j] = f[i - 1][j],这个很容易理解。

 (3)当然,还有一种特殊的情况,就是当前容量的背包放不下当前这一件物品,这种情况下f[i][j] = f[i - 1][j]。 即weight[i] > j

    因此,我们的状态转移方程就是:f[i][j] = max(f[i][j] = f[i - 1][j] , f[i - 1][j - weight[i]] + value[i])  。

主要思路:虽然一开始就知道要放入的物品数和价值,背包大小是m,但是物品是从第一件开始慢慢放入的,而且最开始不是要求背包能否放入物品,而是求放入这件物品时,背包的大小从1开始递增到m,假设这些容量为背包的最大容量,求出各个容量下背包的最大价值,而每个价值作为当前状态下最大价值传递给下一个状态。

为什么要计算出放入第i件物品后,每个容量下最大价值,因为当需要放入第i件商品时,递推公式f[i][j] = max(f[i][j] = f[i - 1][j] , f[i - 1][j - weight[i]] + value[i])  中,f[i - 1][j - weight[i]] + value[i]需要取得放入i-1商品时,在背包容量为j - weight[i]时的价值,才能算出放入后的最大价值。

而最后一个状态,也就是当放入最后一件物品,背包最大容量为m时对应的价值就是最终最大价值。因此核心两个循环,第一层是放入的物品序号,第二层为容量从1递增到m,每一次所取得的结果为:当放完(也可能不放入)第i件物品后,在各容量状态下能取得的最大价值。

 for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (weight[i] > j) {
                f[i][j] = f[i - 1][j];
            }
            else {
                f[i][j] = f[i - 1][j] > f[i - 1][j - weight[i]] + value[i] ? f[i - 1][j] : f[i - 1][j - weight[i]] + value[i];
            }
        }
    }    

例题:https://ac.nowcoder.com/acm/problem/19990 音量调节

该题的核心在于在每一次调音时,要判断是否在该次调音后能否达到每个音量。其实这取决于调音前的音量状态是否能达到。具体可看代码中的分析:

一般该类题目会用一个二维数组f,行序号表示放入第i件物品,列序号表示放入第i件物品后,是否能达到j的音量,用f[i][j]为0或1表示,而背包问题中j表示背包容量,f[i][j]表示此时放入或者不放入的最大价值。

#coding:utf-8
n, beginLevel, maxlevel = map(int, input().split())
change_num_str = input().split()
change_num = [0]
for c in change_num_str:
    change_num.append(int(c))
#print(change_num)
music = [[0]*(maxlevel + 1) for i in range(0, n + 1)] #初始化一个n*maxlevel的矩阵
#music[i][j]为1表示放入第i件物品,音量能达到j;为0表示不能达到j
music[0][beginLevel] = 1
for i in range(1, n + 1):
    # 用背包思路来解决这个问题,需要把每一次循环中的j都看成最终要达到的状态。
    # 那么如果音量需要达到j,就是通过把前一次状态音量调低或者调高。
    # 如果是调低,那么要确保前一次状态j + change_num[i]为1,并且小于maxlevel,这样当前就可以达到音量j
    # 如果是调高,那么要确保前一次状态j - change_num[i]为1,并且>=0,这样当前就可以达到音量j
    for j in range(0, maxlevel + 1):
        if j - change_num[i] >= 0:
            music[i][j] = music[i][j] or music[i - 1][j - change_num[i]]
        if j + change_num[i] <= maxlevel:
            music[i][j] = music[i][j] or music[i - 1][j + change_num[i]]

#逆序输出第一个状态为1的j即可,如果全部为0,则输出-1
flag = 0
for j in range(maxlevel, -1, -1):
    if music[n][j]:
        print(j)
        flag = 1
        break
if flag == 0:
    print(-1)

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值