lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

完全背包问题

问题描述

有N种物品和一个容量为V的背包,每种物品有无限件可用,第i种物品的体积是v[i],价值是w[i],求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

状态定义和状态转移方程

一种常用的状态定义是:dp[i][j]表示前i种物品放入容量为j的背包中能获得的最大价值。

那么对于第i种物品,有以下几种选择:

  • 不放入任何该物品,那么dp[i][j] = dp[i-1][j]
  • 放入1件该物品,那么dp[i][j] = dp[i-1][j-v[i]] + w[i]
  • 放入2件该物品,那么dp[i][j] = dp[i-1][j-2v[i]] + 2w[i]
  • 放入k件该物品,那么dp[i][j] = dp[i-1][j-kv[i]] + kw[i]

其中k的取值范围是0到j/v[i],即不超过背包容量的最大数量。

因此,状态转移方程为:

dp[i][j]=0kj/v[i]max(dp[i1][jkv[i]]+kw[i])

代码实现

根据状态定义和状态转移方程,可以写出二维数组的动态规划代码如下:

int knapsack(int N, int V, int[] v, int[] w) {
    // dp数组初始化为0
    int[][] dp = new int[N+1][V+1];
    // 遍历所有物品
    for (int i = 1; i <= N; i++) {
        // 遍历所有容量
        for (int j = 1; j <= V; j++) {
            // 遍历所有选择次数
            for (int k = 0; k <= j / v[i]; k++) {
                // 更新最大价值
                dp[i][j] = max(dp[i][j], dp[i-1][j-k*v[i]] + k*w[i]);
            }
        }
    }
    // 返回最终结果
    return dp[N][V];
}

这个代码的时间复杂度是O(NVS),其中S是所有物品数量之和。

优化方法

为了优化时间复杂度,可以对状态转移方程进行化简。观察上面的方程,可以发现当k=0时,dp[i][j] = dp[i-1][j];当k=1时,dp[i][j] = dp[i-1][j-v[i]] + w[i];当k=2时,dp[i][j] = dp[i-1][j-2v[i]] + 2w[i]…以此类推。

如果将k替换为k+1,并且将j替换为j-v[i],可以得到:

dp[i][jv[i]]=0k(jv[i])/v[i]max(dp[i1][j(k+1)v[i]]+(k+1)w[i])

将两个方程相减,并化简得到:

dp[i][j]dp[i][jv[i]]=0kj/v[i]max(dp[i1][jkv[i]]+kw[i])0k(jv[i])/v[i]max(dp[i1][j(k+1)v[i]]+(k+1)w[i])

注意到,当k取最大值j/v[i]时,第一个max中的项等于dp[i-1][j];当k取最大值(j-v[i])/v[i]时,第二个max中的项等于dp[i-1][j-v[i]]。因此,上式可以化为:

dp[i][j]dp[i][jv[i]]=dp[i1][j]dp[i1][jv[i]]

移项得到:

dp[i][j]=dp[i1][j]+dp[i][jv[i]]

这就是化简后的状态转移方程,它的含义是:对于第i种物品,可以选择不放入,那么价值等于前i-1种物品放入容量为j的背包的最大价值;也可以选择放入,那么价值等于前i种物品放入容量为j-v[i]的背包的最大价值加上当前物品的价值。

根据这个方程,可以写出一维数组的动态规划代码如下:

int knapsack(int N, int V, int[] v, int[] w) {
    // dp数组初始化为0
    int[] dp = new int[V+1];
    // 遍历所有物品
    for (int i = 1; i <= N; i++) {
        // 遍历所有容量
        for (int j = v[i]; j <= V; j++) {
            // 更新最大价值
            dp[j] = max(dp[j], dp[j-v[i]] + w[i]);
        }
    }
    // 返回最终结果
    return dp[V];
}

这个代码的时间复杂度是O(N*V),空间复杂度是O(V)。

参考链接:

1: 完全背包问题 - 力扣(LeetCode):

【动态规划/背包问题】详解「完全背包」问题 & 三种背包问题之间的内在关系 - 知乎:

算法之动态规划(DP)求解完全背包问题 - CSDN博客:

经典动态规划:完全背包问题 - labuladong - 博客园:

动态规划—完全背包问题详解 - DarkerG - 博客园

posted on 2023-06-28 21:09  白露~  阅读(19)  评论(0编辑  收藏  举报