01背包问题

给定n种物品,和一个容量为C的背包,物品i的重量是w[i],其价值为v[i]。问如何选择装入背包的物品,使得装入背包中的总价值最大?(面对每个武平,只能有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入物品多次)

声明一个数组f[n][c]的二维数组,f[i][j]表示在面对第i件物品,且背包容量为j时所能获得的最大价值。

根据题目要求进行打表查找相关的边界和规律

根据打表列写相关的状态转移方程

用程序实现状态转移方程

真题演练:

一个旅行者有一个最多能装M公斤的背包,现在有n件物品,它们的重量分别是W1、W2、W3、W4、…、Wn。它们的价值分别是C1、C3、C2、…、Cn,求旅行者能获得最大价值。

输入描述:

第一行:两个整数,M(背包容量,M<= 200)和N(物品数量,N<=30);

第2…N+1行:每行两个整数Wi,Ci,表示每个物品的质量与价值。

输出描述:

仅一行,一个数,表示最大总价值

样例:

输入:

10 4

2 1

3 3

4 5

7 9

输出:

12

解题步骤

定义一个数组dp[i][j]表示容量为j时,拿第i个物品时所能获取的最大价值。

按照题目要求进行打表,列出对应的dp表。

W[i](质量)

V[i](价值)

0

1

2

3

4

5

6

7

8

9

10

0

0

0

0

0

0

0

0

0

0

0

2

1

0

0

1

1

1

1

1

1

1

1

1

3

3

0

0

1

3

3

4

4

4

4

4

4

4

5

0

0

1

3

5

5

6

8

8

9

9

7

9

0

0

1

3

5

5

6

9

9

10

12

对于一个动态规划问题设置下标时最好从0开始,因为动态规划经常会和上一个状态有关系!从上面的dp表可以看出来对于一个物品我们拿还是不难需要进行两步来判断。第一步:判断背包当前的容量j是否大于物品当前的质量,如果物品的质量大于背包的容量那么就舍弃。第二步:如果背包可以装下这个物品,就需要判断装下该物品获取的最大价值是不是大于不装下这个物品所获取的最大价值,如果大于那么就把东西装下!根据这样的思想我们可以得到状态转移方程:

如果单签背包的容量可以装下物品:

dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);

如果当前背包的容量装不下该物品:

dp[i][j]=dp[i-1][j];

#include

int max(const int a,const int b)

{

return a>b ? a:b;

}

int main()

{

int w[35]={0},v[35]={0},dp[35][210]={0};

int n,m;

scanf("%d %d",&m,&n);

int i,j;

for(i=1;i<=n;i++){

scanf("%d %d",&w[i],&v[i]);

}

for(i=1;i<=n;i++){

for(j=1;j<=m;j++){

if(j>=w[i])//如果当前背包的容量大于商品的质量

{

dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);//判断是否应该拿下

}

else//大于背包的当前容量

{

dp[i][j]=dp[i-1][j];

}

}

}

for(int k=0;k<=n;k++)

{

for(int l=0;l<=m;l++)

{

printf("%d ",dp[k][l]);

}

printf("\n");

}

printf("%d\n",dp[n][m]);

}

通过运行以上程序可以看到最终的输出dp表和我们的预期是相符合的!但是并没有结束,动态规划有一个后无效性原则(当前状态只与前一个状态有关)。那么我们就可以对dp数组进行压缩处理,将二维数组转换成一维数组。每一次选择物品对这个数组进行更新就可以啦!那么就可以将状态转移方程压缩成为 dp[j]=max(dp[j],dp[j-w[i]]+v[i]) 。不过我们需要注意的是在压缩过后我们需要逆序刷新数组的值,如果正序刷新的话就不能保存上一个阶段对应获取最大价值的值了。那么我们就可以写出以下程序:

#include

int max(const int a,const int b)

{

return a>b ? a:b;

}

int main()

{

int w[35]={0},v[35]={0},dp[210]={0};

int n,m;

scanf("%d %d",&m,&n);

int i,j;

for(i=1;i<=n;i++){

scanf("%d %d",&w[i],&v[i]);

}

for(i=1;i<=n;i++){

for(j=m;j>=0;j--){

if(j>=w[i])//如果当前背包的容量大于商品的质量

{

dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//判断是否应该拿下

}

}

for(int k=0;k<=m;k++)

{

printf("%d ",dp[k]);

}

printf("\n");

}

printf("%d\n",dp[n][m]);

}

可以看出和上面输出的dp表并没有什么区别!

完全背包问题

题目描述:

设有n种物品,每种物品有一个重量及一个价值,但每种物品的数量都是无限的,有一个背包,最大载重量为m,今从n中物品中选取若干件(同一种物品可以多次选取)使其质量小于等于m,而价值的和为最大。

输入:

第一行:两个整数,M(背包容量,M<= 200)和N(物品数量,N<=30);

第2…N+1行:每行两个整数Wi,Ci,表示每个物品的质量与价值。

输出:

仅一行,一个数,表示最大总价值。

样例:

输入:

10 4

2 1

3 3

4 5

7 9

输出:

12

与01背包问题不同的是这不是每个物品选择拿与不拿的问题了,而是要选择几个该物品,最终选择价值最大的。那么我们可以在01背包的问题上继续进行思考这个问题,01背包中我们知道了之前的状态,那么我无非就是要判断拿k个物品和不拿时进行比较,如果价值比之前大就拿下。而每个种类的物品最多只能拿取j/w[i]个,再加一重循环不就可以啦!程序的核心代码如下:

for(i=1;i<=n;i++){

for(j=m;j>=0;j--){

for(k=0;k<=j/w[i];k++)

{

dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);//判断是否应该拿下k个商品

}

}

}

通过代码可以发现通过这种朴素的算法是需要三重循环的,好像对时间复杂度比较高。那么我们也借鉴01背包来对完全背包进行打表!

w[i](质量)

v[i](价值)

0

1

2

3

4

5

6

7

8

9

10

0

0

0

0

0

0

0

0

0

0

0

2

1

0

0

1

1

2

2

3

3

4

4

5

3

3

0

0

1

3

3

4

6

6

7

9

9

4

5

0

0

1

3

5

5

6

8

10

10

11

7

9

0

0

1

3

5

5

6

9

10

10

12

根据表中红色标记的数值来看,需要注意的是如果背包的容量不能装下当前物品的质量。那么当前容量所能装下价值最大的物品就等于上一个物品所能保存的最大价值。重点看一下4是怎么来的,这个4并不是从 i-1来的,而是从i来的。通过正序推出该物品的价值。状态转移方程就可以写成是 :dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]) 和01背包的唯一区别是max的第二个参数。01背包是i-1,而完全背包是i,而且是通过正序推理得到的状态转移方程。

根据状态转移方程应该很快就能写出程序了吧!但是根据dp的后无效性原则,对动态规划状态转移方程进行压缩!压缩过后就是dp[j]=max(dp[j],dp[j-w[i]]+v[i]) ,小伙伴们一看是不是和01背包的状态转移方程一模一样啊!但是但是两个有个重大的区别:01背包使用的是上一条的数据,所以需要逆序避免覆盖之前的值,而完全背包是从当前更新后的数据进行相关的操作的 。通过以上分析我们可以写出如下程序:

#include

int max(const int a,const int b)

{

return a>b ? a:b;

}

int main()

{

int w[35]={0},v[35]={0},dp[210]={0};

int n,m;

scanf("%d %d",&m,&n);

int i,j;

for(i=1;i<=n;i++){

scanf("%d %d",&w[i],&v[i]);

}

for(i=1;i<=n;i++){

for(j=0;j<=m;j++){

if(j>=w[i])//如果当前背包的容量大于商品的质量

{

dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//判断是否应该拿下

}

}

for(int k=0;k<=m;k++)

{

printf("%d ",dp[k]);

}

printf("\n");

}

printf("%d\n",dp[m]);

}

通过以上代码的输出可以看出打印的dp表和我们推测的并没有什么区别,程序正确!

多重背包问题

题目描述:

为了庆祝班级在校运会上取得了全校第一名的好成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能够购买最大价值的奖品,可以补充他们的精力和体力。

输入:

第一行输入两个数n(n<=500),m(m<=6000),其中n代表希望购买的奖品的种数,m表示拨款金额。

接下来的n行,每行3个数,w,v,s分别表示第i种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可),其中w<=100,v<=1000,s<=10;

输出:

一行:一个数,表示此次购买能获得的最大价值(注意!不是价格)。

示例:

输入:

5 1000

输出:

80 20 4

40 50 9

30 50 7

40 30 6

20 20 1

与完全背包不同的是:完全背包每个物品的个数是无限的,而多重背包是每个物品只能拿指定的件数。那么最容易想到的方法就是把相同的物品分开,比如说有n个a物品,就将它分成a1 a2 a3 a4…an然后用01背包的方法去解决。那么我们就可以写出以下核心代码:

for(i=1;i<=n;i++){

for(j=m;j>=0;j--){

for(k=0;k<=s[i]&&j>=k*w[i];k++)

{

dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);//从01背包的状态转移方程,去增加第i个物品拿k个的循环

}

}

}

通过以上的代码可以看出当s[i]特别大的时候那么就会消耗非常多的时间复杂度,那么肯定是有优化的方法的!那么我们可以通过二进制来对这个同一个物品应该拿取几个进行优化。我们可以通过以下问题进行研究:

有1000个苹果,10个箱子怎么放,不管我想拿多少个苹果,都可以成箱成箱的拿?

用二进制的思想就是每一个箱子代表二进制对应的位数,那么210大于1024应该是可以满足题目条件的。那么每个箱子放的苹果分别是1,2,4,8,16,32,…488(1000-512)。需要一个苹果拿第一箱,需要两个苹果拿第二项,需要三个苹果拿一二箱。那么对于需要拿1000箱的问题本来要循环1000次,经过优化以后只用循环10次就可以啦!那么我们就可以写出以下程序啦!

for(i=1;i<=n;i++){

for(j=m;j>=0;j--){

for(k=0;k<=s[i]&&j>=k*w[i];k<<=1)

{

if((k<<1)>s[i]&&j>=k*w[i])

{

dp[j]=max(dp[j],dp[j-(s[i]-k)*w[i]]+(s[i]-k)*v[i]);

}

else

dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);//从01背包的状态转移方程,去增加第i个物品拿k个的循环

}

}

}

动态规划解题思路

对于动态规划问题我们一般的思路如下:

判断是动态规划的解题思路以后立马定义一个数组,把数组对应的下标、对应的值想清楚。

然后根据题目意思找规律进行打表,找出初始状态以及一些边界条件。

根据打表的数据找出状态转移方程。

最后根据状态转移方程进行编写程序。

到此这篇关于C语言动态规划之背包问题详解的文章就介绍到这了,更多相关C语言 背包内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

c语言背包问题装字母,C语言动态规划之背包问题详解相关推荐

  1. 电大计算机C语言1253,1253《C语言程序设计》电大期末精彩试题及其问题详解

    1253<C语言程序设计>电大期末精彩试题及其问题详解 (34页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.90 积分 实用文档&l ...

  2. 状态压缩动态规划部分习题详解

    状态压缩动态规划部分习题详解 状压DP部分题目详解 状态压缩动态规划部分习题详解 简介 经典子集类问题 原子弹 最短路与状压DP结合 送礼物 P3959宝藏 旅游 经典网格类 铺地砖 一笔画 其他类型 ...

  3. c语言程序设计移动字母,C语言程序设计实例大全(220个例子)

    C语言程序设计实例大全,C语言是世界上最流行.使用最广泛的高级程序设计语言之一,这里c语言 程序设计 实例 大全有220个例子供大家参考 c语言开发实例目录: 第一部分 基础篇 001 第一个C程序 ...

  4. c语言程序设计移动字母,c语言程序设计word版.pdf

    c语言程序设计word版 程序设计语言一般分为机器语言.汇编语言和高级语言三大类. 1.机器语言 对于计算机来说,一组机器指令就是程序,称为机器语言程序. 2.汇编语言 为了便于理解与记忆,人们采用能 ...

  5. C语言 程序的翻译 预处理 编译 汇编 链接 #define详解

    1.程序的翻译环境和执行环境 执行环境:所在操作系统的平台 win10 win11 linux 翻译环境:MSVC gcc g++ 你的vs 2019 和2022 是集成开发环境把编辑器编译器全部给你 ...

  6. c语言windows驱动编程入门,Windows驱动开发技术详解 PDF扫描版[175MB]

    Windows驱动开发技术详解由浅入深.循序渐进地介绍了windows驱动程序的开发方法与调试技巧.本书共分23章,内容涵盖了windows操作系统的基本原理.nt驱动程序与wdm驱动程序的构造.驱动 ...

  7. c语言中用来指示文件缓冲区中具体读写位置,C语言文件读写操作中缓冲区问题和setbuf函数详解...

    清除和设置文件缓冲区 (1).清除文件缓冲区函数: int fflush(FILE *stream); int flushall(); fflush()函数将清除由stream指向的文件缓冲区里的内容 ...

  8. c语言关键字extern作用,C语言中extern关键字详解

    <C语言中extern关键字详解>由会员分享,可在线阅读,更多相关<C语言中extern关键字详解(5页珍藏版)>请在人人文库网上搜索. 1.C 语言中 extern 关键字详 ...

  9. c语言中自定义函数返回数组,C语言自定义函数返回数组的方法(图文详解)

    C语言自定义函数返回数组的方法(图文详解) 最近看到一些同学问题,有提到说:如何在一个函数中返回数组呢? 能否直接在自定义 函数中,写成char *类型返回值,直接返回呢?,代码如下: 直接返回str ...

最新文章

  1. 小白该如何学习Linux操作系统(2)
  2. easyui-treegrid移除树节点出错
  3. MyBatis学习总结[4]-ResultMap子元素
  4. 关于GPU-driver for linux的资料
  5. 操作系统(四)操作系统的运行机制
  6. 【读书笔记】2015年考研英语二真题翻译(帮你克服艰难之路的真理+熟路效应)
  7. ABAP内表数据和JSON格式互转
  8. BlogEngine学习一:操作符重载
  9. 可通过http获取远端服务信息_(二)NebuLogMvcSample如何获取应用日志并定制输出...
  10. MyBatis 动态 SQL(认真看看, 以后写 SQL 就爽多了)
  11. MQ发送的消息都到了死信队列中了
  12. ios14描述文件无法与服务器连接,iOS14屏蔽更新描述文件已损坏,无法安装的解决办法...
  13. foxpro获取html数据类型,FoxPro数据库写入html文件中
  14. java在regedit找不到_Windows找不到文件regedit打不开注册表的解决办法
  15. 【绝知此事要躬行】线性表之数组OJ
  16. html画圣诞树—动态效果展示【炫酷合集】
  17. CANopen--基于DS402协议的伺服电机原点回零模式实现
  18. 117道Java面试题及答案(大多都是项目里面所用到的技术点)
  19. 《Operating System Concepts(操作系统概念)》课程学习(1)——Chapter 1 Introduction(第1章 绪论)
  20. OpenGL如何画球体?

热门文章

  1. 【springboot】绿盾解密不求人
  2. USB HID读卡器 M1卡读卡器 Mifare 读写器 NFC方案 NFC读写器方案 usb 接口NFC读写器 ISO14444 TypeA 协议读写器 S50/S70 usb读写器 提供Demo
  3. Python脚本运行出现语法错误
  4. 手写spring简单实现转账--体会核心ioc和aop
  5. 安装实时查看日志工具 log.io
  6. sesehtneraPdilaV.20
  7. 【Oracle 数据库】奶妈式教程 day13 日期函数
  8. 阿里云要分拆上市,腾讯云、百度云跟不跟?
  9. Java帝国之宫廷内斗(2)
  10. myeclipse重新编译java,反编译class文件并重新编译的方法