当前位置:   article > 正文

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

完全整数背包问题

引入

(题目来自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
  • 1
  • 2
  • 3
  • 4
  • 5

输出样例

10
  • 1

分析

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

状态表示

不再多说,定义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]);
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

二维优化!

上边的为什么慢呢?因为有三重循环
老样子,让我们观察状态转移方程
(以下用 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);
  • 1

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

     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]);
             }
         }
     }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

一维优化!

直接做等价变形,删去一维的 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]);
         }
     }
  • 1
  • 2
  • 3
  • 4
  • 5

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

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号