动态规划背包问题(今日心得)

背包问题

0/1背包

题意概述

在这里插入图片描述看完题意之后,模拟整个走的过程。
在这里插入图片描述
状态转移方程如下

cin >> n >> m ;
   for (int i = 1 ; i <=n ; ++i)
   {
       cin >> v[i] >> w[i] ;  // 体积,价值
   }
   for (int i = 1 ; i <= n ; ++i)
   {
       for (int j = 1 ; j <= m ; ++j)
       {
           if (j - v[i] >= 0)
           {
               f[i][j] = max(f[i-1][j] , f[i-1][j-v[i]] + w[i]) ;
           }else
           {
               f[i][j] = f[i-1][j] ;
           }   
       }
   }

学过滚动数组,都可以知道此道题可以优化成一维数组,如果题目中不要求你算出整个过程,就可以采取这种办法,来达到优化空间的效果。

滚动数组概述

滚动数组其实就是一个递推的过程,我们学过的斐波那契数列和爬楼梯都可以采取这种办法来解决,例如 p = [1,2,3] ;现在让你算出这个数组的前缀和,那么就是[1,3,6],这就是一个简单的滚动数组,那么这道题目怎么化为一维的呢?有状态方程可以知道,f[i][j]的值只依赖于f[i-1]层,因此可以把f[i-1]全部去掉,但是注意一点f[i][j] = f[i-1][j] 其实是没有意义的,这样等价相当于没有改变下标为j的值,所有就不需要这一步,还值得注意的是,f[i][j] = max(f[i-1][j] , f[i-1][j-v[i]] + w[i]) 这个递推式是必须在j >= v[i]下才可以进行的,那么我们把第二层循环改成for (int j = m ; j >= v[i] ; j–),就满足j >= v[i],那么肯定会有人问为什么不改成for (int j = v[i] ; j <= m; ++j),这样不也满足 j >= v[i]吗,这里主要的原因是必须满足0/1背包只能拿一次,例如数组p = [1,2,3,4],我们从前往后进行累加p = [1,3,6,10],你会发现虽然每次加的都是前面一个数,但是其实把前面的前面的数也加上了,如果此时我第i个物品取了一次,dp[j] = max(dp[[j - v[i]] + w[i] , dp[j]) ,按照上述正序去遍历,如果背包为dp[3]在只有第一件物品的时候为6,dp[4] = dp[4-]+6 = 12 ,那么就是说我第一件物品去取了2次了,就和0/1问题相悖,那么如果我的p数组是从后往前的呢?p = [1,2,3,4] , 变化后p = [1,3,5,7],按照这种思维dp[4] = 6 , dp[3] = dp[3-1] + w[i] = 6,不会矛盾。

滚动数组代码

    for (int i = 1 ; i <= n ; ++i) // 遍历物品
   {
       int v , w;
       cin >> v >> w ; 
       for (int j = m ; j >= v ; j--) // 防止一个物品多次添加,并且满足j >= w[i]
       {
           f1[j] = max(f1[j] , f1[j - v] + w) ;
       }
   }

完全背包

这里就不放题目了,就是和0/1背包一样,但是每个物品可以重复放。

题意分析

在前i个物品,容量为m的时候,我们存在两个情况,如果当前容量放不下一个物品i的时候,f[i][j] = f[i - 1][j],如果可以放下那么f[i-1][j - k * v[i]] + k * w[i],有这种思想就可以上代码了。

   for (int i = 1 ; i <= n ; ++i)
   {
       for (int j = 1 ; j <= m ; ++j)
       {
           for (int k = 0 ; k * v[i] <= j ; ++k)
           {
               f[i][j] = max(f[i][j] , f[i-1][j - k * v[i]] + k * w[i]) ;
           }
       }
   }

代码优化

f[i][j] = max (f[i-1][j] , f[i-1][j - 1 * v[i]] + w[i] , f[i-1][j - 2 * v[i]] + 2 * w[i]);
f[i][j - v[i]] = max (f[i-1][j - v] , f[i-1][j - v[i] - 1 * v[i]] + w[i] , f[i-1][j - v[i] - 2 * v[i]] + 2 * w[i] …);
由这两个观察可以知道,f[i][j] = max(f[i-1][j] , f[i][j-v[i]] + w[i]),有了这个我们可以对代码进行再次优化。

    for (int i = 1 ; i <= n ; ++i)
   {
       for (int j = 1 ; j <= m ; ++j)
       {
           if (j >= v[i])
           {
               f[i][j] = max (f[i][j], f[i][j-v[i]] + w[i]) ;
           } else {
               f[i][j] = f[i-1][j] ;
           }
       }
 }

代码优化2

for (int j = v[i] ; j <= m; ++j)为从前往后进行遍历,主要是因为f1[j - v[i]] + w[i],根据上面的递推公式可以分析得知。

     for (int i = 1 ; i <= n ; ++i)
    {
        for (int j = v[i] ; j <= m; ++j)
        {
            f1[j] = max(f1[j] , f1[j - v[i]] + w[i]) ;
        }
    }  

总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值