zoukankan      html  css  js  c++  java
  • 对使用倒序的一维数组解决0/1背包问题的理解 六月飞雪

    对使用倒序的一维数组解决0/1背包问题的理解

    大略的题目:

       N个物品的价值,从这些物品中选出一些(可能全选)并装入容积为V的背包,求背包中的物品的最大价值。

    输入: V,N, 接下来第一行是各个物品的体积v,第二行是各个物品的价值w。

    输出: 背包能装入的物品的最大价值。

    要想理解这个问题,可以使用表格来说明、使用二维数组的解法来做对比。

    测试数据:

    9  3
    5  3  4
    3  5  4

    首先看二维数组代码(已经明白了就跳过):

    View Code
     1 # include <stdio.h>
     2 # include <string.h>
     3 # define max(a, b) a>b ? a : b  //宏替换
     4 
     5 int dp[1010][1010], w[1010], v[1010];   //dp是动态规划的简写,dp[i][j]代表前i个元素装进容量为j的背包的最优解
     6 
     7 int main () {
     8     int V, N;
     9     while (scanf("%d %d", &V, &N) == 2) {
    10         int i;
    11         for (i = 1; i <= N; ++i)
    12             scanf("%d", &v[i]); //输入N个物品的体积
    13         for (i = 1; i <= N; ++i)
    14             scanf("%d", &w[i]); //输入N个物品的价值
    15         memset(dp, 0, sizeof(dp));  //将dp的元素全部初始化为0
    16         int j;
    17         for (i = 1; i <= N; ++i) {
    18             for (j = 0; j <= V; ++j) {
    19                 if (j >= v[i]) {//循环到第i次时,只有容积大于第i个物品的体积才可能装下第i个物品
    20                                 //即dp[i-1][j-v[i]] + w[i] > dp[i-1][j] 才可能成立,此时比较大小才有意义
    21                     dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);   /****状态转移方程****/
    22                     //dp[i-1][j-v[i]] + w[i] 表示前i-1个物品装入容积恰好等于他们的体积之和的背包的最优解再加上第i个物品的价值
    23                     //其实也就是前i个物品装入容积为j的背包(不一定是恰好)
    24                 } else {
    25                     dp[i][j] = dp[i-1][j];//否则装不下第i个物品,所以dp[i][j]等于前i-1个物品装入j的背包的最优解
    26                 }
    27                 if (j == 0) continue;//打印dp表格
    28                 printf("%5d", dp[i][j]);//打印dp表格
    29             }
    30             putchar(10);//打印dp表格
    31         }
    32         printf("%d\n", dp[N][V]);//输出前N个物品装入容积为V的背包的最优解
    33     }
    34 
    35     return 0;
    36 }

    接着看这种解法的dp表

     

    再看一维数组写法:

    (为了和二维数组的代码比较,我做了尽量少的改变)

    View Code
     1 # include <stdio.h>
     2 # include <string.h>
     3 # define max(a, b) a>b ? a : b  //宏替换
     4 
     5 int dp[1010], w[1010], v[1010];   //dp是动态规划的简写,当循环到第i次时,dp[j]代表前i个元素装进容量为j的背包的最优解
     6 
     7 int main () {
     8     int V, N;
     9     while (scanf("%d %d", &V, &N) == 2) {
    10         int i;
    11         for (i = 1; i <= N; ++i)
    12             scanf("%d", &v[i]); //输入N个物品的体积
    13         for (i = 1; i <= N; ++i)
    14             scanf("%d", &w[i]); //输入N个物品的价值
    15         memset(dp, 0, sizeof(dp));  //将dp的元素全部初始化为0
    16         int j;
    17         for (i = 1; i <= N; ++i) {
    18             for (j = V; j >= 0; --j) {
    19                 if (j >= v[i]) {//循环到第i次时,只有容积大于第i个物品的体积才可能装下第i个物品
    20                                 //即dp[j-v[i]] + w[i] > dp[j] 才可能成立,此时比较大小才有意义
    21                     dp[j] = max(dp[j], dp[j-v[i]] + w[i]);   /****状态转移方程****/
    22                     //dp[j-v[i]] + w[i] 当循环到第i次时,
    23                     //表示前i-1个物品装入容积恰好等于他们的体积之和的背包的最优解再加上第i个物品的价值
    24                     //其实也就是前i个物品装入容积为j的背包(不一定是恰好)
    25                 } else {
    26                     dp[j] = dp[j];//否则装不下第i个物品,所以dp[j]等于前i-1个物品装入j的背包的最优解
    27                 }
    28                 if (j == 0) continue;//打印dp表格
    29                 printf("%5d", dp[j]);//打印dp表格
    30             }
    31             putchar(10);//打印dp表格
    32         }
    33         printf("%d\n", dp[V]);//输出前N个物品装入容积为V的背包的最优解
    34     }
    35 
    36     return 0;
    37 }

    接着看这种解法的dp表:

      

    可以发现,这两种写法导致了dp表左右相反。

    二维dp写法的状态转移公式表明当前的dp值需要用dp表上面一个数值和左上的数值来确定。

    那么一维dp写法是怎么实现状态的转换的呢?根据状态转换公式dp[j]可能等于dp[j](循环到i时的dp[j]和循环到i-1时的dp[j]是不同的! )即上面一个数值,

    也可能等于dp[j-v[i]],即右上的数值。(这点比较难想到,原先我以为和二维的一样是由左上和上面的值推出来的)

    为什么一维数组写法不能用顺序呢?我们看一下顺序dp表:

    比较一下二维写法的dp表可以发现第2行、第三行的第6列之后的数值不对了,为什么呢?

    以第二行第六列为例,此时i = 2, j = 6。dp[6] = max(dp[6], dp[6-v[2]]+w[2])    --①

    也即dp[6] = max(3, dp[6-3]+5)  --②

    也即dp[6] = max(3, 5+5)

    所以dp[6] = 10。

    ①式中的max中的dp[6]是指i=1时的dp[6],而max外的dp[6]是指i=2时的dp[6]。

    ②式中的dp[6-3],即dp[3],是指i=2时的dp[3],而不是i=1时的dp[3],原因是遍历到i=2,j=6时dp[3]已经被更新为i=2层的dp[3],

     但是状态转移方程要求max中的dp是上一层的,而不是当前层的,所以从此开始dp表的值开始出错。

    再返回来看一维dp的倒序写法,由于先更新下标比较大的dp数组元素,此时通过状态转移方程求最大值的时候还未更新下标较小的dp数组元素,

    即下标较小的dp数组元素还是上一层的值,因此倒序的方法可以使用!

    至此,我们就能够全部理解为什么能用一维数组来解决0/1背包问题了。

    本文结束。

  • 相关阅读:
    loj #143. 质数判定
    Quadratic Residues POJ
    P2155 [SDOI2008]沙拉公主的困惑
    P3868 [TJOI2009]猜数字
    P3704 [SDOI2017]数字表格
    P4449 于神之怒加强版
    P2568 GCD
    P1891 疯狂LCM
    loj#6229 这是一道简单的数学题
    P3768 简单的数学题 杜教筛+推式子
  • 原文地址:https://www.cnblogs.com/lanhj/p/2802437.html
Copyright © 2011-2022 走看看