[算法]动态规划之完全背包问题

引入

(题目来自AcWing)

完全背包问题

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

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i种物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例

10

分析

完全背包问题的特点就是不限每个物品的个数
正如之前所说,动态规划分为两步:状态表示状态计算

状态表示

不再多说,定义dp[i][j] 这个集合表示的就是所有选法。
表示前i个物品中选,总体积为j的所有选法的最大值
这一点和01背包一致,这个问题的特点在于,我们的第 i 个物品 是无限的,这将会影响到我们的状态计算。见下:

状态计算

正如上一个博客所讲,我们的状态计算就是 在进行集合划分
01背包问题我们可以分为 0 和 1,也就是要第 i 个物品,或者不要
那么这个问题中无限个数的 第 i 个 物品 ,我们该如何划分呢?
我们可以把这个划分为 k 个集合,集合(t >= 0 && t <= k) 分别代表选 t 个第 i 个物品的选法
这个 k 就是全选物品 i 的不溢出数字, 否则 k 将失去意义。
即 k 要满足:当全部选择物品 i 时,不能让这个背包溢出
也就是 k * volume[i] <= j
那我们思路就很清晰了,有了 01背包的经历,我们很容易想到:
选择 i 个物品 , 背包容量为 j 时,
集合 ki(k >= 0 && k * volume[i] <= j)的值,此时选了 t 个物品 i ,用和 01背包问题选择 物品i 的集合情况 一样的思考方式,就可以转化为 dp[i - 1][j - k * volume[i]] + k * weigh[i],
然后在 fork 循环遍历中,找到这些集合的最大值,就是dp[i][j] 的所要的最大值。
可写出朴素做法

  for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < m; ++j) {
            for (int k = 0; k * volume[i] <= j; ++k) {     
             //最多k个,也就是全装上第 i 个
                dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * volume[i]] + k * weight[i]);
            }
        }
    }

过了!
在这里插入图片描述
但是耗时太长(这个提交我甚至还用了快速读入)
让我们想想如何优化

二维优化!

上边的为什么慢呢?因为有三重循环
老样子,让我们观察状态转移方程
(以下用 v 表示 volumn(i), w 表示 weigh[i] )

dp[i][j] 的值是 k 个集合(k为物品 i 的个数)的最大值

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v] + w, dp[i - 1][j - 2v] + 2w, dp[i - 1][j - 3v] + 3w,…)

再让我们看看dp[i ][j - v] 和它做对比

dp[i][j - v] = max(dp[i - 1][j - v], dp[i - 1][j - v] , dp[i - 1][j - 2v] + w, dp[i - 1][j - 3v] + 2w,…)

我们可以发现,dp[i][j] 和 dp[i][j - v] 只是相差了w。这很好想

有集合A,B
若A中数字均大于B中数字w,那么集合A,B的最大值也一定相差w。

其实从他们两个各自代表的实际意义也能知道,只相差一个物品 i 的价值。

那么我们就能推断出新的状态转移方程

dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - v] + w);

让我们试一试,就能写出:

     for (int i = 1; i <= n; ++i) {
         for (int j = 0; j <= m; ++j) {
             dp[i][j] = dp[i - 1][j];
             if (j >= volume[i]) {
                 dp[i][j] = Math.max(dp[i][j], dp[i][j - volume[i]] + weight[i]);
             }
         }
     }

过了!
在这里插入图片描述
快了很多很多!
但是我们还可以再快一点

一维优化!

直接做等价变形,删去一维的 i 就可以,
dp[i][j] = dp[i - 1][j];变成dp[j] = dp[j];
毫无意义,直接删去即可
又因为下边的if条件,直接让 j 从 volumn[i] 开始遍历
状态转移方程删去一维部分就变成
dp[j] = Math.max(dp[j], dp[j - volume[i]] + weight[i]);

就变成

     for (int i = 1; i <= n; ++i) {
         for (int j = volume[i]; j <= m; ++j) {
             dp[j] = Math.max(dp[j], dp[j - volume[i]] + weight[i]);
         }
     }

过啦!
在这里插入图片描述
更快!

AC代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;


public class Main {
    public static void main(String[] args) throws IOException {
        Input in = new Input();
        int n = in.nextInt();
        int m = in.nextInt();
        int[] volume = new int[n + 1];  //体积(重量)
        int[] weight = new int[n + 1];  //价值(权重)
        for (int i = 1; i <= n; ++i) {
            volume[i] = in.nextInt();
            weight[i] = in.nextInt();
        }

        //分的集合是选择第i个物品选择第k个来划分,且 k * volume[i] <= 容量j
        /** 朴素做法*/
      //  int[][] dp = new int[n + 1][m + 1];
      /*  for (int i = 1; i <= n; ++i) {
            for (int j = 0; j < m; ++j) {
                for (int k = 0; k * volume[i] <= j; ++k) {      //最多k个,也就是全部装第 i 个
                    dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * volume[i]] + k * weight[i]);
                }
            }
        }
      */
        /** 二维优化做法*/
    /*    for (int i = 1; i <= n; ++i) {
            for (int j = 0; j <= m; ++j) {
                dp[i][j] = dp[i - 1][j];
                if (j >= volume[i]) {
                    dp[i][j] = Math.max(dp[i][j], dp[i][j - volume[i]] + weight[i]);
                }
            }
        }
        System.out.println(dp[n][m]);*/

        /**一维优化做法*/
        int[] dp = new int[m + 1];
        for (int i = 1; i <= n; ++i) {
            for (int j = volume[i]; j <= m; ++j) {
                dp[j] = Math.max(dp[j], dp[j - volume[i]] + weight[i]);
            }
        }
        System.out.println(dp[m]);
    }
}


class Input {
    StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public int nextInt() throws IOException {
        in.nextToken();
        return (int) in.nval;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xoliu1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值