动态规划-背包问题总结

举报
Linux猿 发表于 2021/08/05 02:02:49 2021/08/05
【摘要】 01背包问题:                        01背包其实是一个递推问题,每次都是最优解不断推出最终结果。一维和二维都要掌握。 动态方程:...

01背包问题:

                       01背包其实是一个递推问题,每次都是最优解不断推出最终结果。一维和二维都要掌握。

动态方程:    

                   F[ i , v ] = max { F[ i−1 , v ] , F[ i−1 , v−Ci ]  +  Wi } ,意思是:当选第 i 个物品时 F [ i-1 , v ] 代表不放第 i 件物品,F [ i-1 , v - Ci ] + Wi  ; 代表放第 i 件物品,第 i-1 件物品得到的最优解加上第 i 件物品的价值。可以用一维数组  F[ i ] = max (F[ i ] ,F[ i - ci ]+wi ) ;

代码(二维数组):


  
  1. memset(dp,0,sizeof(dp)) ;
  2. for(int i=1 ;i<=n ;i++) // 最终答案为f[n][c]
  3. {
  4. scanf("%d%d",&v,&w) ; // 边输入边计算
  5. for(int j=0 ;j<=c ;j++) // 这一行中 j 不能初始化为 v 因为如果这样 i 时就记录(得)不到
  6. { // i-1 时已经得到的最优解
  7. dp[i][j]=(i==1 ? 0 : dp[i-1][j]) ;
  8. if(j>=v&&dp[i][j]<dp[i-1][j-v]+w)
  9. dp[i][j]=dp[i-1][j-v]+w ;
  10. }
  11. }

代码(一维滚动数组):


  
  1. memset(dp,0,sizeof(dp)) ;
  2. for(int i=1 ;i<=n ;i++)
  3. {
  4. scanf("%d%d",&v,&w) ;
  5. for(int j=C ;j>=v ;j--) // 这一行可以让 j 大于等于 v 因为结束后f[]数组
  6. if(dp[j]<dp[j-v]+w) // 已经把i-1时的最优解保存在里面(因为是一维)
  7. dp[j]=dp[j-v]+w ;
  8. }

题目类型:

               1. 裸题. 2. 有的只给一个价值,只要把价值同时看成体积就可以了. 3. 有的是求一些物品平分问题(可以用搜索).4. 让求恰好装满(理解:初始化的 F 数组事实上就是在没有任何物品可以放 入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为 0 的背包可以在什 么也不装且价值为 0 的情况下被“恰好装满”,其它容量的背包均没有合法的解,属于 未定义的状态,应该被赋值为 -∞ 了。如果背包并非必须被装满,那么任何容量的背包 都有一个合法解“什么都不装”,这个解的价值为 0,所以初始时状态的值也就全部为 0 了).5.让打印路径。

完全背包:

                完全背包只学习了一种方法,二进制方法和多重背包一样。

动态方程:  

               (1) 转化为01背包 f [ i ][ v ] = max { f[ i - 1 ][ v - k*c[ i ] ] + k*w[i] ] { 0<=k*c[i]<=v }

               (2) F[ i ] = max ( F[ i ],F[ i - ci ] + wi ) ; 和 01 背包中的一维数组的一样只是枚举的顺序不同,这里讲一下枚举的循序(背包九讲):首先想想为什么 01 背包中要按照 v 递减的次序来循环。 让 v 递减是为了保证第 i 次循环中的状态 F[i,v] 是由状态 F[i−1,v −Ci] 递推而来。 换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第 i 件物品”这件策 略时,依据的是一个绝无已经选入第 i 件物品的子结果 F[i−1,v −Ci]。而现在完全背 包的特点恰是每种物品可选无限件,所以在考虑“加选一件第 i 种物品”这种策略时, 却正需要一个可能已选入第 i 种物品的子结果 F[i,v−Ci],所以就可以并且必须采用 v 递增的顺序循环。这就是这个简单的程序为何成立的道理。

多重背包:

              多重背包只学了二进制优化二维):


  
  1. 在这之前,我空间好像转过一个背包九讲,现在我就只对
  2. 01背包和多重背包有点印象了
  3. 先说下 01 背包,有n 种不同的物品,每个物品有两个属性
  4. size 体积,value 价值,现在给一个容量为 w 的背包,问
  5. 最多可带走多少价值的物品。
  6. int f[w+1]; //f[x] 表示背包容量为x 时的最大价值
  7. for (int i=0; i<n; i++)
  8. for (int j=w; j>=size[i]; j++)
  9. f[j] = max(f[j], f[j-size[i]]+value[i]);
  10. 如果物品不计件数,就是每个物品不只一件的话,稍微改下即可
  11. for (int i=0; i<n; i++)
  12. for (int j=size[i]; j<=w; j++)
  13. f[j] = max(f[j], f[j-size[i]]+value[i]);
  14. f[w] 即为所求
  15. 初始化分两种情况
  16. 1、如果背包要求正好装满则初始化 f[0] = 0, f[1~w] = -INF;
  17. 2、如果不需要正好装满 f[0~v] = 0;
  18. 多重背包问题要求很简单,就是每件物品给出确定的件数,求
  19. 可得到的最大价值
  20. 多重背包转换成 01 背包问题就是多了个初始化,把它的件数C 用
  21. 分解成若干个件数的集合,这里面数字可以组合成任意小于等于C
  22. 的件数,而且不会重复,之所以叫二进制分解,是因为这样分解可
  23. 以用数字的二进制形式来解释
  24. 比如:7的二进制 7 = 111 它可以分解成 001 010 100 这三个数可以
  25. 组合成任意小于等于7 的数,而且每种组合都会得到不同的数
  26. 15 = 1111 可分解成 0001 0010 0100 1000 四个数字
  27. 如果13 = 1101 则分解为 0001 0010 0100 0110 前三个数字可以组合成
  28. 7以内任意一个数,加上 0110 = 6 可以组合成任意一个大于6 小于13
  29. 的数,虽然有重复但总是能把 13 以内所有的数都考虑到了,基于这种
  30. 思想去把多件物品转换为,多种一件物品,就可用01 背包求解了。
  31. 看代码:
  32. int n; //输入有多少种物品
  33. int c; //每种物品有多少件
  34. int v; //每种物品的价值
  35. int s; //每种物品的尺寸
  36. int count = 0; //分解后可得到多少种物品
  37. int value[MAX]; //用来保存分解后的物品价值
  38. int size[MAX]; //用来保存分解后物品体积
  39. scanf("%d", &n); //先输入有多少种物品,接下来对每种物品进行分解
  40. while (n--)
  41. { //接下来输入n中这个物品
  42. scanf("%d%d%d", &c, &s, &v); //输入每种物品的数目和价值
  43. for (int k=1; k<=c; k<<=1) { //<<右移 相当于乘二
  44. value[count] = k*v;
  45. size[count++] = k*s;
  46. c -= k;
  47. }
  48. if (c > 0) {
  49. value[count] = c*v;
  50. size[count++] = c*s;
  51. }
  52. }
  53. 现在用count 代替 n 就和01 背包问题完全一样了

分组背包(背包九讲):

      有n件物品和一个容量为V的背包,第 i 件物品的费用是 Ci ,价值是 Wi , 这些物品被划分为 k 组,每组中的物品相互冲突,最多选一件,求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

算法:

这个问题变成了每组物品有若干中策略:是选择本组的某一件,还是一件都不选。也就是说设F[ k,v ] 表示前 k 组物品花费费用 v 能取得的最大权值,则有:

                    F[ k , v ] = max { F[ k-1 , v ] ,F[k-1,v-Ci ] +Wi } ;

同样可以优化为一维。假设有 k 组物品每组有 m 个物品:


  
  1. for(int i=0 ;i<k ;i++) // 组数
  2. for(int j=V ;j>=0 ;j--) // V 为总体积
  3. for(int k=0 ;k<m&&v[i][k]<=j ;k++) // 依次放入同组的每个物品
  4. dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]) ;

开始一直不明白这样为什么能做到每组物品只选择一件,自己模拟了一下才恍然大悟,因为放同一组的时候体积是逆序放的,而且放同一体积时同一组的都放一次,这样就保证了每组物品最多选一件(可以不选择)。

变形:每组至少选择一件(例题:HDU 3033 )。

   如果这样初始化时就要注意了,只有第 0 组所有的情况是正确的(组数从 1 开始),so ~ > 其它赋值为负无穷。因为每组物品可以选择多件,因此要把第二层 for 循环与第三层for 循环互换一下。这样就可以保证每组物品可以选择多件。

代码~~>

推荐博客:01背包总结 建议先看背包九讲         

文章来源: blog.csdn.net,作者:Linux猿,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/nyist_zxp/article/details/18078085

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。