写在前面:我是一只蒟蒻~~~

今天我们要讲讲动态规划中~~最最最最最~~~~简单~~的背包问题


1. 首先,我们先介绍一下

 01背包

大家先看一下这道01背包的问题  
题目  
有m件物品和一个容量为n的背包。第i件物品的大小是w[i],价值是k[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。    
题目分析:
我们刚刚看到这个题目时,有的人可能会第一想到贪心,但是经过实际操作后你会很~~神奇~~的发现,贪心并不能很好的解决这道题(没错,本蒟蒻就是这么错出来的)。这个时候就需要我们非常强大的动态规划(DP)出马。  
  我们可以看出,本题主要的一个特点就是关于物品的选与不选。这时候我们就会想如何去处理,才可以使我们装的物品价值总和最大,而且这道题的物品只有一个,要么选一个,要么不选。所以这个时候我们就可以推出它的状态转移方程(啥!你不知道啥是状态转移方程?那你自行理解吧)。  
    我们设f[i][j]为其状态。就有了以下式子
 1 f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+k[i]); 
  i表示件数,j表示空间大小。  
  f[i][j]就表示i件物品下背包空间为j的状态。  
  f[i-1][j]表示在i-1件时背包空间为j的状态(在这中间则代表了在i件时不取这件物品)。   
  f[i-1][j-w[i]]+k[i]表示取这件物品后该背包的空间为j-w[i],而总价值则增加了k[i]。   
  可能会有人问,这个式子跟我的贪心式子比有什么不一样的吗?  
  当然,这个式子能切掉这道题而贪心不行(这不是废话吗!!!)
   嗯,说重点,这个式子只是牵扯到i-1件物品的问题,与其他无关,所以这就很好的解决了贪心对全局的影响。  
   可以显而易见的是其时间复杂度O(mn)(m是件数,n是枚举的空间)已经很优秀了,但是它的空间复杂度还是比较高,所以我们就可以使用一维数组进行优化,具体怎样优化,我们下面再说。   
   好了,说完这一题的核心码我们就可以得出f[m][n]所得到的是最优解。(为什么??!!,如果你还不理解的话那我建议你上手动模拟一下,当然你也可以进入这里看一下是怎么操作的。
 嗯,这道题就结束了,我们来一道确切存在的题目(洛谷)P1060 开心的金明
         下面就是这道题的AC代码(如果你看懂了上面,代码就不难理解了)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m;
 4 int f[30][30007],w[30],v[30],k[30];//根据题目要求设置变量,f就表示状态
 5 void dp(){
 6     memset(f,0,sizeof(f));//初始化(一般可忽略)
 7     for(int i=1;i<=m;i++){//枚举物品数量
 8         for(int j=w[i];j<=n;j++){//枚举背包空间
 9             if(j>=w[i]){//如果背包空间能够装下下一件物品进行状态转移
10                 f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+k[i]);//转移方程
11             }
12         }
13     }
14 }
15 int main(){
16     scanf("%d%d",&n,&m);
17     for(int i=1;i<=m;i++){
18         cin>>w[i]>>v[i];
19         k[i]=w[i]*v[i];//读入+处理
20     }
21     dp();//进行处理
22     printf("%d",f[m][n]);
23     return 0;
24 }

这里对于01背包的讲解基本就结束了,下面给大家推荐几道题来练习,P1164 小A点菜      P1048 采药    P1049 装箱问题  。    
     
 最后,我来填一下我上面留下来的坑,如何优化二维01背包的空间复杂度。  
 很简单,就是把二维变为一维(啥!你说不明白?)这难道不是很显然的事情吗?你从f[i][j]变为f[i]直接缩小一维,空间不就小了一维吗。好了,下面,我们就谈谈如何实现的减维。   
 我们知道枚举从1~i来算出来f[i][j]的状态。所以,我们是不是可以用一个f[j]来表示每地i次循环结束后是f[i][j]的状态,而f[i][j]是由max(f[i-1][j],f[i-1][j-w[i]]+k[i])递推出来的,而我们只有从j=n到0的顺序进行枚举,这样才能保证推f[j]时f[j-w[i]]保存的是f[i-1][j-w[i]]的状态值。   
     核心代码

1 for(int i=1;i<=m;i++){
2     for(int j=n;j>=w[i];j--){
3         f[j]=max(f[j],f[j-w[i]]+k[i]);
4     }
5 }

这是一种比较好的写法,但还有的人(~~比如说我~~)就喜欢这样写(因为我很~~勤奋~~)

1 for(int i=1;i<=m;i++){
2     for(int j=n;j>=0;j--){
3         if(j>=w[i]){
4             f[j]=max(f[j],f[j-w[i]]+k[i]);
5            }
6     }
7 }

这样我们都可以达到我们优化空间复杂度的目的(当然,我推荐大家写第一种,这样就不用担心判断大小的问题了)。  
    掌握这个优化其实十分重要的,有的题会卡二维数组的空间,这样我们只能用一维数组进行解题。   
    嗯,01背包就讲到这里了,希望能够帮到各位Oier,如有错误,请指出,本人定改正。

----手动分割一波=^ω^= ------


2、了解完01背包,我们来看一看

完全背包

老规矩,上题。  
题目(P1616 疯狂的采药):由于本蒟蒻~~比较懒~~,请大家点开自行看题。   
下面进行题目分析:   
我们不难看出,完全背包与01背包只是物品数量的不同,一个是只有1个,而物品的情况也只有    取和不取。但完全背包却是有无数多个,这就牵扯到一个物品取与不取和取多少的问题。这是的时间复杂度就不再是O(nm)了。而经过一些优化(这里给大家一个地址,大家可以在这里去看一看,本蒟蒻就不再展开讲解)  
既然大家都已经明白了怎样进行优化(哪来的已经啊!!!假装假装吗≥﹏≤)  
不管怎么说,我们就可以得到这个转移方程  
 1 f[j]=max(f[j],f[j-w[i]]+c[i]); 
相信大家在理解01背包后,对完全背包的状态转移方程理解容易些。
其中的思想还是和01背包是相同的。    
下面贴出AC代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int T,n,v[10007],t[10007],f[100007];//变量的定义,f[]表示状态
 4
 5 int main(){
 6     scanf("%d%d",&T,&n);//读入
 7     for(int i=1;i<=n;i++){
 8         cin>>t[i]>>v[i];
 9     }
10     memset(f,0,sizeof(f));//初始化(一般可忽略)
11     for(int i=1;i<=n;i++)//枚举物品i
12     {
13         for(int j=t[i];j<=T;j++){//背包空间(必须从t[i]开始,由于数量是无限的,所以,我们必须要递增枚举)
14                 f[j]=max(f[j],f[j-t[i]]+v[i]);//状态转移
15         }
16     }
17     cout<<f[T];//输出答案
18 }

综上,就是完全背包的讲解,由于我懒,所以就不给大家推荐题了,我相信大家一定能够练习好的,嗯!我相信大家。(相信什么相信,快点干活!!(粉笔飞来)我闪 嗯,不存在的,正中靶心。   
咳咳!我们来推荐最后一道题P2918 [USACO08NOV]买干草Buying Hay这一题希望大家好好想一想,有点坑,但是并不是太难,大家加油吧!!!!!


3、下一个,本蒟蒻不会!!!!

多重背包  

等我学会,再来更新~~~~~  
送给大家一个博客背包九讲


hello!我又回来了,今天我就来给大家来讲一讲我上回留下来的坑。

首先,我们先介绍一下何为多重背包

问题描述:

多重背包:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
     
 这里,我们可以看到多重背包与完全背包和01背包多不同的在于每件物品有有限多个,所以我们就产生了一种思路,那就是:将多重背包的物品拆分成01背包~~

这样一来,我们就可以用01背包的套路来解决这个问题,而这个代码呢,也很简单:

1 for(int i=1;i<=n;i++){
2     for(int j=1;j<=num[i];j++){
3         a[++cnt]=v[i];
4     }
5 }

这样一来,我们就可以十分简单的解决这道题了!!!

但是,简单归简单,我们可以看到这个时间复杂度是十分不优秀的,所以我们可以想一想如何优化,

这时候我们来考虑一下进制的方法,

二进制
 首先,我们先补充一个结论,就是1~n以内的数,都能够通过n进制以内的数组合得到。

这样的话,我们就可以通过二进制的拆分来进行优化,我们把每个物品有的所有个数,分开,

核心代码:

1 for(int i=1;i<=6;i++){
2             for(int j=1;j<=num[i];j<<=1){
3                 v[++cnt]=a[i]*j;
4                 num[i]-=j;
5             }
6             if(num[i]>0)v[++cnt]=num[i]*a[i];//如果还有剩余,就全部加入
7         }

下面,我们来看一道例题:
题目描述:

POJ1742 Coins

总时间限制: 
3000ms
内存限制: 
65536kB
描述
People in Silverland use coins.They have coins of value A1,A2,A3...An Silverland dollar.One day Tony opened his money-box and found there were some coins.He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch.
You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.
输入
The input contains several test cases. The first line of each test case contains two integers n(1<=n<=100),m(m<=100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1<=Ai<=100000,1<=Ci<=1000). The last test case is followed by two zeros.
输出
For each test case output the answer on a single line.
样例输入
3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0
样例输出
8
4

  这是什么意思呢?

我大概给大家翻译一下(原谅我蒟蒻的英语)

就是什么意思吧,给定N种硬币,其中第i种硬币的面值为Ai,共有Ci个。从中选出若干个硬币,把面值相加,若结果为S,则称“面值S能被拼成”。求1~M之间能被拼成的面值有多少个。

题目分析:

我们看到题目中给的是一个可行性的问题,我们只需要依次考虑每种硬币是否被用于拼成最终的面值,以“已经考虑过的物品种数”i作为dp的阶段,在阶段i时我们用f[i]表示前i种硬币能否拼成面值j。

法1:(朴素拆分发)

代码:

 1 bool f[100010];
 2 memset(f,0,sizeof(f));
 3 f[0]=1;
 4 for(int i=1;i<=;i++){
 5     for(int j=1;j<=c[i];j++){
 6         for(int k=m;k>=a[i];k--){
 7             f[k]+=f[k-a[i]];
 8         }
 9     }
10 }
11 int ans=0;
12 for(int i=1;i<=m;i++){
13     ans+=f[i];
14 }
15  

这个题,这样解的话时间复杂度就太高,所以我们转换一个思路,来进行二进制拆分,

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define maxn 3004
 4 int f[maxn][maxn],a[maxn],b[maxn],n;
 5
 6 int main(){
 7     scanf("%d",&n);
 8     for(int i=1;i<=n;i++){
 9         scanf("%d",&a[i]);
10     }//读入
11     for(int i=1;i<=n;i++){
12         scanf("%d",&b[i]);
13     }
14     for(int i=1;i<=n;i++){
15         int val=0;//val代表f[i-1][j]
16         if(b[0]<a[i])val=f[i-1][0];
17         for(int j=1;j<=n;j++){
18              if(b[j]==a[i])f[i][j]=val+1;
19             else f[i][j]=f[i-1][j];//转移
20             if(b[j]<a[i])val=max(val,f[i-1][j]);//判断
21         }
22     }
23     int maxx=0;
24     for(int i=1;i<=n;i++){
25         maxx=max(maxx,f[n][i]);
26     }
27     printf("%d\n",maxx);
28
29     return 0;
30 }

下面,我们来看一下另一道题:

划分大理石

题目描述:

描述

有价值分别为1..6的大理石各a[1..6]块,现要将它们分成两部分,使得两部分价值之和相等,问是否可以实现。其中大理石的总数不超过20000。

输入格式

有多组数据!
所以可能有多行
如果有0 0 0 0 0 0表示输入文件结束
其余的行为6个整数

输出格式

有多少行可行数据就有几行输出
如果划分成功,输出Can,否则Can't

样例输入

4 7 4 5 9 1
9 8 1 7 2 4
6 6 8 5 9 2
1 6 6 1 0 7
5 9 3 8 8 4
0 0 0 0 0 0

样例输出

Can't
Can
Can't
Can't
Can

看完这道题,我们不难看出,这是一道与P1164 小A点菜 十分相似的题,其中的不同点就是一个是01背包,一个是多重背包,所以我们就可以先用二进制进行拆分,然后再跑一遍DP即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int num[7],a[7],dp[500007],v[100008],sum,cnt;
int main(){for(int i=1;i<=6;i++)a[i]=i;while(scanf("%d%d%d%d%d%d",&num[1],&num[2],&num[3],&num[4],&num[5],&num[6])){if(!num[1]&&!num[2]&&!num[3]&&!num[4]&&!num[5]&&!num[6])break;sum=0;memset(v,0,sizeof(v));memset(dp,0,sizeof(dp));for(int i=1;i<=6;i++)sum+=(a[i]*num[i]);
//        printf("%d\n",sum);if(sum%2==1){printf("Can't\n");continue;}sum=sum/2;cnt=0;for(int i=1;i<=6;i++){for(int j=1;j<=num[i];j<<=1){v[++cnt]=a[i]*j;num[i]-=j;}if(num[i]>0)v[++cnt]=num[i]*a[i];//如果还有剩余,就全部加入
        }dp[0]=1;for(int i=1;i<=cnt;i++){for(int j=sum;j>=v[i];j--){dp[j]+=dp[j-v[i]];}}if(dp[sum])printf("Can\n");else printf("Can't\n");}return 0;
}

好了,今天就讲到这了。

后续背包推荐题目( 持续更新……):洛谷P1156(最优可将状态降至一维)  ,洛谷P1417(背包加排序的组合)  ,洛谷P5020 (经数学证明后,实质为完全背包) ,

转载于:https://www.cnblogs.com/xishirujin/p/10574413.html

背包问题(01背包,完全背包,多重背包(朴素算法二进制优化))相关推荐

  1. 背包问题-【01背包】【完全背包】【多重背包】【多限定条件背包】

    背包问题 给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高. 可参考https://www.cnblogs.com/-guz/p/9866118.h ...

  2. 小明打联盟 牛客(背包dp,多重背包)

    链接:https://ac.nowcoder.com/acm/problem/14553 来源:牛客网 题目描述 小明很喜欢打游戏,现在已知一个新英雄即将推出,他同样拥有四个技能,其中三个小技能的释放 ...

  3. 【python】一篇讲透背包问题(01背包 完全背包 多重背包 二维费用背包)

    面对背包问题,有一个很重要的方程式:状态转移方程式 所以每一种背包问题我都会给出状态转移方程式 #01背包 什么是01背包型问题? 先给大家感受一下01背包型问题: 给定n种物品和一背包.物品i的重量 ...

  4. c++ 多重背包状态转移方程_【模板】各种背包问题amp;讲解

    [模板]各种背包问题&讲解  背包问题集合 一般来说,动态规划(DP)都是初学者最难闯过的一关,而在这里详细解说动态规划的一种经典题型:背包问题. 这里介绍的背包分为以下几种:01背包,完全背 ...

  5. 01背包模板、全然背包 and 多重背包(模板)

    转载请注明出处:http://blog.csdn.net/u012860063 贴一个自觉得解说不错的链接:http://www.cppblog.com/tanky-woo/archive/2010/ ...

  6. 01背包模板、完全背包 and 多重背包

    转载请注明出处:http://blog.csdn.net/u012860063 讲解链接:http://www.cppblog.com/tanky-woo/archive/2010/07/31/121 ...

  7. 【qduoj - 142】 多重背包(0-1背包的另类处理,dp)

    题干: ycb的ACM进阶之路 Description ycb是个天资聪颖的孩子,他的梦想是成为世界上最伟大的ACMer.为此,他想拜附近最有威望的dalao为师.dalao为了判断他的资质,给他出了 ...

  8. (step3.3) hdu 1059(Dividing——多重背包)

    题目大意:分别给出价值为1~6的石头的数量.问能否将这些石头等价值平分... 解题思路:多重背包 1)多重背包的典型描述是这样的:给出n种物品,背包的容量为V.每种物品的可用数量为num[i],所占体 ...

  9. ZCMU 1919: kirito's 星爆气流斩【01背包的二进制优化】

    ZCMU 1919: kirito's 星爆气流斩 Time Limit: 2 Sec  Memory Limit: 128 MB Description 主角kirito是使用世界首款完全潜行游戏& ...

最新文章

  1. win7拒绝访问_win7系统提示无法访问application data如何解决
  2. 事务管理最佳实践全面解析
  3. Cinder 的 I/O QoS
  4. 递归python首尾相同的子字符串_推荐Python语言多层嵌套list的递归处理方法
  5. server 2008 配置php mysql_Win2008 Server配置PHP环境
  6. Centos7 安装gitlab 8.7.5
  7. js构造函数内存在的闭包
  8. ONNX系列三 --- 使用ONNX使PyTorch AI模型可移植
  9. java垃圾回收机制介绍
  10. cad快看_浩辰CAD看图王教你免费打开超大CAD图纸!
  11. 高精度定位网络PAS的经济模式_RTK基站网络共享奖励模式
  12. 智慧交通再改革,电子警察抓拍通信传输解决方案
  13. xrander/cvt自定义分辨率
  14. 二进制十进制十六进制转换_二进制数制到十进制数制的转换
  15. Vue3使用axios的配置教程
  16. 代码中的下划线_是什么意思呢?
  17. 刘韧马杰花总:诗歌小说电影游戏都是元宇宙
  18. Java实现自动发聊天消息
  19. 生日蛋糕-python实现
  20. 珍惜能在公司上班的每一天——2022年个人工作总结

热门文章

  1. 游戏编程模式 - 观察者模式
  2. Linux下的压缩解压缩工具(转载)
  3. 高精度计算(三)压位
  4. Grafana画图表教程
  5. 用python做一张图片_用 Python 生成一张有“内涵”的图片
  6. Android Things:外设I/O接口-UART
  7. APP调用支付宝支付
  8. 成功解决百度网盘下载文件时遇到 下载总进度一直处于99.9%,显示一直下载不下来的问题
  9. java 制作自定义控件_C#下自定义控件的制作 一
  10. matlab分段函数的表达_【MATLAB】06_分段函数的多种表示方法