华师一附中OI组

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 3539|回复: 12
打印 上一主题 下一主题

背包问题由浅入深

[复制链接]

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
跳转到指定楼层
楼主
发表于 2018-9-8 22:08:52 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
背包问题是一类物品选择问题,通常是求在一定条件下选择某些不同属性的物品以获得最大的评价值。典型题目如下:
1、1克,3克,4克,7克,8克的砝码各1个,能不能凑成10克的重量?
2、1克,3克,4克,7克,8克的砝码各4个,能不能凑成10克的重量?
3、1克,3克,4克,7克,8克的砝码无穷个,4个砝码以内能不能凑成10克的重量?
4、1克,3克,4克,7克,8克的金块各1个,价值分别是10元,25元,35元,80元,100元,不超过10克重量的情况下价值最大是多少元?
5、1克,3克,4克,7克,8克的金块各10个,价值分别是10元,25元,35元,80元,100元,不超过10克重量的情况下价值最大是多少元?
6、1克,3克,4克,7克,8克的金块各1个,价值分别是10元,25元,35元,80元,100元,恰好重量为10克的情况下价值最大是多少元?
7、1克,3克,4克,7克,8克的金块各1个,价值分别是10元,25元,35元,80元,100元,体积分别是1立方厘米,2立方厘米,3立方厘米,5立方厘米,6立方厘米,重量不超过10克,体积不超过10立方厘米的情况下价值最大是多少元?(似乎有点违背物理常识)
8、1克,3克,4克,7克,8克的金块各1个,价值分别是10元,25元,35元,80元,100元,凑到10克以上的重量至少需要多少钱?
9、1克,3克,4克,7克,8克的金块各1个,价值分别是10元,25元,35元,80元,100元,奇数克的砝码(1,3,7)至多选1个,不超过10克重量的情况下价值最大是多少元?
===




回复

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
推荐
 楼主| 发表于 2018-9-19 10:51:20 | 只看该作者
第五题,仿照第二题,加一维,枚举每个砝码的个数,参考代码如下
回复 支持 1 反对 0

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
推荐
 楼主| 发表于 2018-9-10 19:05:55 | 只看该作者
第二题,砝码的个数不是1个而是多个,有个笨笨的办法就是变成20个砝码来做这个题,11113333444477778888这样20个砝码,也可以在选择砝码的时候加上一个循环,枚举砝码被选择的次数,然后对结果进行||运算
  1. #include <iostream>
  2. using namespace std;
  3. int a[]= {0,1,3,4,7,8};
  4. int b[]= {0,4,4,4,4,4};
  5. bool f[6][30];///f[i][j]表示前面i个砝码能否凑成j克的重量
  6. int x,r,c,k;
  7. bool t1,t2;
  8. int main()
  9. {
  10.     cin>>x;
  11.     f[0][0]=1;
  12.     for (r=1; r<=5; r++)
  13.         for (c=0; c<=20; c++)
  14.         {
  15.             t1=0;///记录初始值
  16.             for (k=0; k<=b[r]; k++) ///枚举可能的情况
  17.             {
  18.                 if (k*a[r]<=c) t2=f[r-1][c-k*a[r]];///若能装下
  19.                 t1=t1||t2; ///或者!!!!
  20.             }
  21.             f[r][c]=t1;
  22.         }

  23.     for (r=0; r<=5; r++)  ///输出表格检查
  24.     {
  25.         for (c=0; c<=20; c++) cout<<f[r][c]<<' ';
  26.         cout<<endl;
  27.     }
  28.     if (f[5][x]) cout<<'Y';
  29.     else cout<<'N';
  30.     return 0;
  31. }
复制代码


回复 支持 1 反对 0

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
推荐
 楼主| 发表于 2018-9-9 17:36:35 | 只看该作者
第一题:1克,3克,4克,7克,8克的砝码各1个,能不能凑成10克的重量?
显然最朴素的做法就是枚举五种砝码,选或者不选,看看能不能凑成10克,朴素的代码如下:
  1. #include <iostream>
  2. using namespace std;
  3. int a[]= {0,1,3,4,7,8};  ///砝码的重量
  4. int a1,a2,a3,a4,a5;///记录是否被选
  5. int x;
  6. bool bb;
  7. int main()
  8. {
  9.     cin>>x;
  10.     bb=0;
  11.     for (a1=0; a1<=1; a1++)
  12.         for (a2=0; a2<=1; a2++)
  13.             for (a3=0; a3<=1; a3++)
  14.                 for (a4=0; a4<=1; a4++)
  15.                     for (a5=0; a5<=1; a5++) ///五种循环穷举
  16.                         if  (a1*a[1]+a2*a[2]+a3*a[3]+a4*a[4]+a5*a[5]==x)
  17.                         {
  18.                             bb=1;
  19.                             cout<<a1<<a2<<a3<<a4<<a5<<endl; ///输出可选方案
  20.                         }
  21.     if (bb) cout<<'Y';
  22.     else cout<<'N';
  23.     return 0;
  24. }
复制代码


但这么做程序代码不可控,运行效率也很差。想想要是10个砝码怎么办?那不是要写10个循环,n个砝码呢?当然有人说用dfs写x选y的程序,那是另外一个做法,但是效率依然不高。
我们采用DP列表法来做此题,用fij表示前i个砝码能否凑出j这个重量,那么显然
f00=1   不用砝码也可以得到0克
fij=fi-1jh或者f(i-1)(j-ai)  前面不选ai能否得到j 后面是选择ai那么前面i-1个需要凑成j-ai克的重量,在电子表格中画个表看看这个迭代的过程。
于是编程如下:
  1. #include <iostream>
  2. using namespace std;
  3. int a[]= {0,1,3,4,7,8};
  4. bool f[6][30];///f[i][j]表示前面i个砝码能否凑成j克的重量
  5. int x,r,c;
  6. bool t1,t2;
  7. int main()
  8. {
  9.     cin>>x;
  10.     f[0][0]=1;
  11.     for (r=1; r<=5; r++)
  12.         for (c=0; c<=20; c++)
  13.         {
  14.             t1=f[r-1][c]; ///不用此砝码
  15.             if (c>=a[r]) t2=f[r-1][c-a[r]];  ///用此砝码,那么要保证c-a[r]>=0
  16.             else t2=0;
  17.             f[r][c]=t1||t2;  ///或者操作,有一个满足就行
  18.         }

  19.     for (r=0; r<=5; r++)  ///输出表格检查
  20.     {
  21.         for (c=0; c<=20; c++) cout<<f[r][c]<<' ';
  22.         cout<<endl;
  23.     }
  24.     if (f[5][x]) cout<<'Y';
  25.     else cout<<'N';
  26.     return 0;
  27. }
复制代码

有同学很快的发现,类似杨辉三角,这个二维数组可以写成一维的,这个对于初学者,我们就不强求了。
回复 支持 1 反对 0

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
5#
 楼主| 发表于 2018-9-10 19:06:00 | 只看该作者
第三题,限定砝码总个数最多4个,所以我们在记录这个重量能不能被凑出来的时候就应该记录所用的最少砝码数而不是简单的记录是都被凑出来。就以1 3 4 7 8 为例,f(3,4)是前3个砝码凑出重量4的最少砝码数量,应该是1。所以每次枚举最少砝码数,取其中的最小值。为了清晰和简便,凑不出来不用0表示,而用一个很大的数字,比如999代替。
  1. #include <iostream>
  2. #include <iomanip>
  3. using namespace std;
  4. int a[]= {0,1,3,4,7,8};
  5. int b[]= {0,4,4,4,4,4};
  6. int f[6][30];///f[i][j]表示前面i个砝码最少需要几个凑成j克的重量
  7. int x,r,c,k;
  8. int t1,t2;
  9. int main()
  10. {
  11.     cin>>x;
  12.     for (r=0; r<=5; r++)
  13.         for (c=0; c<=29; c++) f[r][c]=999;
  14.     f[0][0]=0;
  15.     for (r=1; r<=5; r++)
  16.         for (c=0; c<=20; c++)
  17.         {
  18.             t1=999;///记录初始值
  19.             for (k=0; k<=b[r]; k++) ///枚举可能的情况
  20.             {
  21.                 if (k*a[r]<=c) t2=f[r-1][c-k*a[r]]+k;///若能装下
  22.                 t1=min(t1,t2);
  23.             }
  24.             f[r][c]=t1;
  25.         }

  26.     for (r=0; r<=5; r++)  ///输出表格检查
  27.     {
  28.         for (c=0; c<=20; c++) cout<<setw(4)<<f[r][c]<<' ';
  29.         cout<<endl;
  30.     }
  31.     if (f[5][x]) cout<<'Y';
  32.     else cout<<'N';
  33.     return 0;
  34. }
复制代码


回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
6#
 楼主| 发表于 2018-9-19 10:14:55 | 只看该作者
第四题,这是我们传统意义上的基本背包问题,给定n个物品,每个物品有重量和价值,求最大重量一定情况下的价值。
我们仿照上面的例子,还是用f(i,j)表示前面i个物品能够凑成的,重量不超过j的时候价值的最大值。
那么应该可以想得到  f(i,j)=f(i-1,j)(不选第i个物品)或者 f(i-1, j-wi)+vj (选择第i个物品,这个时候要注意j>=wi)
初始值f(0,0)=0也是显然的。于是程序如下:
  1. ///标准背包
  2. #include <iostream>
  3. #include <iomanip>
  4. using namespace std;
  5. int w[]= {0,1,3,4,7,8};
  6. int v[]= {0,10,25,35,80,100};
  7. int f[6][200];///f[i][j]表示前面i个砝码在不超过j克的重量的情况下的最大价值
  8. int x,r,c,k;
  9. int t1,t2;
  10. int main()
  11. {
  12.     for (r=0; r<=5; r++)
  13.             for (c=0; c<=199; c++) f[r][c]=0;
  14.             
  15.     for (r=1; r<=5; r++)
  16.         for (c=0; c<=20; c++)
  17.         {
  18.             t1=f[r-1][c];
  19.             if (w[r]<=c) t2=f[r-1][c-w[r]]+v[r];///若能装下
  20.             else t2=0;
  21.             f[r][c]=max(t1,t2);
  22.         }

  23.     for (r=0; r<=5; r++)  ///输出表格检查
  24.     {
  25.         for (c=0; c<=20; c++) cout<<setw(4)<<f[r][c]<<' ';
  26.         cout<<endl;
  27.     }
  28.     return 0;
  29. }
复制代码


回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
7#
 楼主| 发表于 2018-9-19 13:18:27 | 只看该作者
以上都是简单的背包问题,我们用的填表大法,二维数组比较直观,但是有点浪费,其实也可以一维数组,那么就是本地迭代。在此之前,请认真体会fibo数列,辗转相除求最大公约数,杨辉三角的一维数组做法。

请看第一题,1,3,4,7,8的砝码各一个,能不能拼出10克的重量,我们用一维数组bi表示i克能否拼出,那么初始的时候,全部是0.
b0=1,
先来一个1克的砝码b1=1
再来一个3克的 因为b0=b1=1,所以b(0+3)=b(1+3)=1
再来一个4克的。因为b0=b1=b3=b4=1所以 b(0+4)=b(1+4)=b(3+4)=b(4+4)=1
**
就是说从每来一个砝码,就把这个所有的为1的重量的加上这个砝码的重量设为1为了避免重复累加,应该类似杨辉三角,从后往前推。(!!!!!)
代码如下:

  1. #include <iostream>
  2. using namespace std;
  3. int a[]= {0,1,3,4,7,8};
  4. bool f[30];///表示能否凑出
  5. int x,i,j;
  6. int main()
  7. {
  8.     f[0]=1;
  9.     for (i=1; i<=5; i++)  ///枚举5个砝码
  10.     {
  11.         for(j=29; j>=a[i]; j--) ///由后向前
  12.             f[j]=f[j]||f[j-a[i]];  ///或者我可以 ,或者j-a[i]可以
  13.     }
  14.     for(j=0; j<=29; j++) cout<<f[j]<<' ';
  15.     return 0;
  16. }
复制代码

第四题是标准的背包问题,用一维数组的解法如下,和前面有点不同的地方是这个判断标准,上面用的是或者运算,这里用的是求最大值的运算
  1. #include <iostream>
  2. using namespace std;
  3. int w[]= {0,1,3,4,7,8};
  4. int v[]= {0,10,25,35,80,100};
  5. int f[30];
  6. int x,i,j;
  7. bool t1,t2;
  8. int main()
  9. {

  10.     for (i=1; i<=5; i++)  ///枚举5个砝码
  11.     {
  12.         for(j=29; j>=w[i]; j--) ///由后向前
  13.             f[j]=max(f[j],f[j-w[i]]+v[i]);  ///或者我可以 ,或者j-a[i]可以
  14.     }
  15.     for(j=0; j<=29; j++) cout<<f[j]<<' ';
  16.     return 0;
  17. }
复制代码


回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
8#
 楼主| 发表于 2018-9-28 10:15:25 | 只看该作者
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
9#
 楼主| 发表于 2018-10-2 09:43:38 | 只看该作者
第一阶段总结:
1、简单背包问题就是填那个二维的表格,行对应每个物品的重量,列对应背包的容量重量,里面的元素看情况不同,也许是01表示此重量可否凑出,也许是可以凑出此重量的最小物品个数,或者是不超过此重量的最大价值。
2、标准是用二重循环,但是注意行列的顺序,外层循环枚举循环物品,里层循环枚举重量列表;
3、也可以用一维数组,但是注意要从后向前,
4、初始值有的时候是0,有的时候是999,有的时候是-999,想想为什么?
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
10#
 楼主| 发表于 2018-10-2 19:47:32 | 只看该作者
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|服务支持:DZ动力|华师一附中OI组  

GMT+8, 2024-11-2 04:28 , Processed in 0.125404 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表