背包问题.docx
- 文档编号:9542960
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:31
- 大小:27.48KB
背包问题.docx
《背包问题.docx》由会员分享,可在线阅读,更多相关《背包问题.docx(31页珍藏版)》请在冰豆网上搜索。
背包问题
背包问题
常州一中林厚从
背包问题是信息学奥赛中的经典问题。
背包问题可以分为0-1背包和部分背包两种类型,0-1背包还可以再分为有限背包和无限背包(完全背包)。
背包问题的求解涉及到贪心、递归、递推、动态规划、搜索等多种算法。
熟练掌握各种背包问题及其变形试题的解法,是信息学奥赛选手从入门走向提高的必经之路。
先简单归纳一下涉及到的这几种重要算法:
1、贪心:
贪心法可以归纳为“每步取优”。
假设你的程序要走1~n共n步,则你只要保证在第i步(i=1..n)时走出的这一步是最优的。
所以,贪心法不是穷举,而只是一种每步都取优的走法。
但由于目光短浅,不考虑整体和全局,所以“步步最优”并不能保证最后的结果最优。
比如经典的“两头取数”问题、“n个整数连接成最大数”问题、“删数”问题等。
2、递归:
递归算法可以归纳为将问题“由大化小”。
也就是将一个大问题分解为若干个“性质相同”的子问题,求解的的过程,一般是通过“函数的递归调用”,不断将大问题逐步细化、直至元问题(边界情况),最后通过递归函数的自动返回得到问题的解。
递归算法的关键是递归函数的构造,它的效率往往比较低,原因在于大量的“冗余”计算。
比如经典的“斐波那挈数列”问题,在递归实现时效率极低,存在着大量的冗余计算,可以采用“记忆化”的方法优化。
3、递推:
递推问题往往有一个“递推公式”,其实和“递归公式”差不多,但是出发点不一样,递归的思想是“要想求什么就要先求出什么”。
而递推是从问题的边界情况(初始状态)出发,一步步往下走,直到走完n步,判断最后的解。
由于其中的每一步并不知道当前一步的哪一个值对后面的步骤有用,所以只能把所有情况(一步的所有走法)全部计算出来,也造成了很多的“冗余计算”。
时间上往往没有太多的优化余地,但空间上经常利用“滚动数组”等方式,把空间复杂度由O(n2)降到O(2n)。
比如经典的“杨辉三角形”问题、“判断n是否是斐波那挈数”问题等。
4、动态规划:
本质上是一种克服了“冗余”的“递归”算法。
它依然是不断地把一个大问题分解为若干个性质相同的子问题,而这些子问题里面会存在着很多的重叠。
为了高效地解决问题,不重复计算出现的重叠子问题,往往在求解的时候采用“递推”的方法,由小到大,按“阶段”一步一步生成。
所以如果重叠的子问题越多,则动态规划相比直接递归求解来说,效率就越高。
使用动态规划算法的前提条件是“无后效性”和“最优子结构”。
5、搜索:
就是一种带有策略的“穷举”。
实现方法多种多样,在这儿我们主要是指递归搜索(深搜)。
比如经典的“n的有序拆分”问题。
搜索的效率往往也是比较低的,一般可以通过合理的“剪枝”来优化。
第1节部分背包
所谓部分背包是指每件物品都可以取出部分装入背包中,而不是要么不要、要么全要。
例1、有一个背包,能承受的最大重量为max。
另有n件物品,第i件物品的重量为w[i]、价值为p[i],且每件物品都可以任意拆分。
求该背包能装入的物品最大总价值?
[算法分析]
方法:
贪心法
先计算所有物品的单位价值(即P[i]/W[i]的值),这个值越大,表明该物品的单位价值越大,那越是应该优先选择装入背包中。
此处的贪心策略是可以通过“反证法”严格证明的。
但并不是所有题目的贪心策略都可以方便地证明的,有些看似正确,其实贪心法并不能得到正确的解。
在有限的竞赛时间内,一般都是通过尝试不断找反例来验证自己的贪心策略的正确性。
Programex1a;
Constmaxn=100;
Varp,w:
array[1..maxn]ofreal;
temp,max,total:
real;
n,i,j:
integer;
Begin
Readln(n,max);{读入物品数及背包容量}
Fori:
=1tondo{读入每件物品的重量、价值}
Readln(w[i],p[i]);
fori:
=1ton-1do{按单位价值降序排列}
forj:
=i+1tondo
ifp[i]*w[j]
begin
temp:
=p[i];p[i]:
=p[j];p[j]:
=temp;
temp:
=w[i];w[i]:
=w[j];w[j]:
=temp;
end;
total:
=0;
fori:
=1tondo{从单位价值大的开始,贪心选取物品}
ifw[i]<=maxthenbegintotal:
=total+p[i];max:
=max-w[i];end{可以全取}
elsebegintotal:
=total+p[i]/w[i]*max;break;end;{只能取部分}
writeln(total:
0:
2);
end.
[测试数据]
输入:
5100
102
203
303
405
506
输出:
13.6
第2节0-1背包
所谓0-1背包,就是指一件物品是不可分的,要么不取(0)、要么全取
(1)。
根据每件物品的数量多少,0-1背包可以再分为0-1有限背包(每件物品只有1个)和0-1无限背包(完全背包),有时还有所谓的多重背包(即每件物品都有K[i]个)。
一、0-1有限背包
1、求最多可放入的体积
例2、装箱问题
[问题描述]
有一个箱子容量为maxv(正整数,0≤maxv≤20000),同时有n件物品(0≤n≤30),每件物品有一个体积vi(正整数)和一个价值pi(正整数)。
要求从这n件物品中任取若干件装入箱内,使箱子的剩余空间最小。
[输入样例]
1003{maxv,n}
704020{v1,v2,……,vn}
[样例输出]
10
[算法分析]
方法1:
搜索
Programex2a;
constmaxn=31;
vari,best,n,maxv:
integer;
v,s:
array[0..maxn]ofinteger;{设s[n]为前n件物品的体积和}
proceduresearch(k,restv:
integer);{搜索第k个物品,当前剩余空间为restv}
begin
ifrestv =restv; ifrestv-(s[n]-s[k-1])>=bestthenexit; {如果把剩下的全部放进去,得到的解也不会比当前的更优,那么就剪枝} ifk<=nthenbegin ifrestv>=v[k]thensearch(k+1,restv-v[k]); {如果第k件物品可以放进去,那么就递归放第k+1件物品} search(k+1,restv);{如果第k件物品不放,则对第k+1件物品递归} end; end; begin{main} readln(maxv,n); fori: =1tondoread(v[i]); best: =maxv;{best用来记录最后的答案,初始化为箱子的容量} fillchar(s,sizeof(s),0); fori: =1tondo s[i]: =s[i-1]+v[i];{预处理} search(1,maxv);{从第1件物品开始搜,当前剩余体积为maxv} writeln(best); end. 方法2: 动态规划 设F[i,j]为一布尔型数组,表示在前i件物品中选择若干个放入箱子,其占用的体积正好为j的情况。 问题的解为maxv减去所有满足F[n,j]=true的j的最大值。 这样,就将一个最优化问题转化为一个判定性问题。 而: F[i,j]=F[i-1,j-v[i]](v[i]<=j<=maxv) 边界: F[0,0]: =true 以上为未经优化的二维动态规划,主要的程序代码如下: f[0,0]: =true; fori: =1tondo forj: =maxvdowntov[i]do{注意与后面无限背包的区别} f[i,j]: =f[i-1,j]orf[i-1,j-v[i]]; fori: =maxvdownto1do iff[n,i]thenbegin best: =maxv-i; break; end; F[i,j]的变化: 由上一行往下一行逐步推,红色的T表示值的变化。 j=0 1 … 20 … 30 … 40 … 60 … 70 … 90 … 100 i=0 T F F F F F F F F 1 FT F F F F F FT F F 2 FT F F F FT F FT F F 3(n) FT F FT F FT FT FT FT F 观察发现,其实当前状态只与前一阶段状态有关,可以降至一维数组实现。 即用F[i]表示任意选取若干件物品,占用体积为i的情况可否实现,实现方法如下: F[0]: =true; Fori: =1tondo Forj: =maxvdowntov[i]do{注意与后面无限背包的区别} F[j]: =F[j]orF[j-v[i]]; 参考程序如下: Programex2b; constmaxn=31; vari,j,n,maxv: integer; v: array[0..maxn]ofinteger; f: array[0..20000]ofboolean; begin readln(maxv,n); fori: =1tondoread(v[i]); fori: =1tomaxvdof[i]: =false; f[0]: =true; fori: =1tondo forj: =maxvdowntov[i]do f[j]: =f[j]orf[j-v[i]]; fori: =maxvdownto1do iff[i]then beginwriteln(maxv-i);halt;end; end. [测试数据] 输入: 105 12345 输出: 0 2、求可以放入的最大价值 [算法分析] 方法1: 搜索 Programex2c; constmaxn=31; vari,best,n,maxv: integer; v,p,s: array[0..maxn]ofinteger;{设s[i]为前i件物品的总价值} proceduresearch(k,restv,ps: integer); {搜索第k件物品,当前剩余空间为restv,当前总价值为ps} begin ifps>=bestthenbest: =ps;{如果当前价值比当前最优答案优,则修改当前最优答案} ifps+(s[n]-s[k-1]) ifk<=nthenbegin ifrestv>=v[k]thensearch(k+1,restv-v[k],ps+p[k]);{取该物品} search(k+1,restv,ps);{不取该物品} end; end; begin{main} readln(maxv,n); fori: =1tondoread(v[i]); fori: =1tondoread(p[i]); best: =0; fillchar(s,sizeof(s),0); fori: =1tondo s[i]: =s[i-1]+p[i]; search(1,maxv,0); writeln(best); end. [测试数据] 输入: 1003 704020 504030 输出: 80 输入: 105 12345 54321 输出: 14 方法2: 动态规划 根据第n件物品取与不取的两种情况,可以得到动态转移方程为: f[n,maxv]=max{f[n-1,maxv-v[n]],f[n-1,maxv]}。 也可以降为一维: f[j]=max{f[j-v[i]]+p[i],f[j]},j从maxv到v[i],i从1到n。 Programex2d; constmaxn=31; vari,j,best,n,maxv: integer; v,p: array[0..maxn]ofinteger; f: array[0..20000]ofinteger; begin readln(maxv,n); fori: =1tondoread(v[i]); fori: =1tondoread(p[i]); fillchar(f,sizeof(f),0); best: =0; fori: =1tondo forj: =maxvdowntov[i]do begin iff[j-v[i]]+p[i]>f[j]thenf[j]: =f[j-v[i]]+p[i]; iff[j]>bestthenbest: =f[j]; end; writeln(best); end. 3、求恰好装满的情况数 [算法分析] 方法: 动态规划 转移方程为: f[n,v]=f[n-1,v-w[n]]+f[n-1,v],表示从n件物品中取若干件,组成的体积为v的情况数,f[0,0]=1。 Programex2e; constmaxn=31; vari,j,n,maxv: integer; v: array[0..maxn]ofinteger; f: array[0..maxn,0..20000]ofinteger; begin readln(maxv,n); fori: =1tondoread(v[i]); fillchar(f,sizeof(f),0); f[0,0]: =1; fori: =1tondo forj: =0tomaxvdo ifj=0thenf[i,j]: =1 elseifj>=v[i]thenf[i,j]: =f[i-1,j-v[i]]+f[i-1,j] elsef[i,j]: =f[i-1,j]; writeln(f[n,maxv]); end. [测试数据] 输入: 105 12345 输出: 3 二、0-1无限背包(完全背包) 例3、采药改编 把题目的每株草药都改成: 每种草药的数量都足够多 样例的输出为: 140 [算法分析] 问题就变成了0/1无限背包问题,可以用动态规划做,问题就是求采摘第m种草药需要的时间所能达到的的最大价值。 设方程f(j)表示取第i种草药(i为变量),花去时间j所能达到的最大价值,则动态转移方程为: f(j)=max(f(j),f(j-time[i])+p[i])。 Programex3; vartime: array[1..100]oflongint; p: array[1..100]oflongint; f: array[0..1000]oflongint; i,ans,t,m,j: longint; functionmax(a,b: longint): longint; begin ifa>bthenmax: =aelsemax: =b; end; begin{main} readln(t,m); fori: =1tomdoreadln(time[i],p[i]); fori: =0totdof[i]: =0; fori: =1tomdo forj: =time[i]totdo f[j]: =max(f[j],f[j-time[i]]+p[i]); ans: =0; fori: =1totdoans: =max(ans,f[i]); writeln(ans); end. 三、0-1有限背包的变形——多重背包 例4、多重背包 [问题题目] 有N种物品和一个容量为V的背包。 第i种物品最多有m[i]件可用,每件体积是v[i],价值是w[i]。 求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。 [输入样例] 1003{maxv,n} 704020{v1,v2,……,vn} 504030{p1,p2,……,pn} 123{m1,m2,m3……,mn} [算法分析] 本题和0-1无限背包问题很类似。 基本的方程只需将0-1无限背包问题的方程略微一改即可,因为对于第i种物品有m[i]+1种策略: 取0件,取1件……取m[i]件。 令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则: f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=m[i]}。 复杂度是O(V*∑m[i])。 另一种好想好写的基本方法是转化为0-1有限背包求解: 把第i种物品换成m[i]件0-1有限背包中的物品,则得到了物品数为∑m[i]的01有限背包问题,直接求解,复杂度仍然是O(V*∑m[i])。 但是我们期望将它转化为01有限背包问题之后能够像0-1无限背包一样降低复杂度。 应用二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0..m[i]件——均能等价于取若干件代换以后的物品。 另外,取超过m[i]件的策略必不能出现。 方法是: 将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。 使这些系数分别为1,2,4,...,2^(k-1),m[i]-2^k+1,且k是满足m[i]-2^k+1>0的最大整数。 例如,如果m[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。 分成的这几件物品的系数和为m[i],表明不可能取多于m[i]件的第i种物品。 另外这种方法也能保证对于0..m[i]间的每一个整数,均可以用若干个系数的和表示。 这样就将第i种物品分成了(logm[i])种物品,将原问题转化为了复杂度为O(V*∑logm[i])的0-1有限背包问题,是很大的改进。 参考程序如下: Programex4; constmaxn=31; vari,j,best,n,maxv,k,t: integer; v,p,num,v1,p1: array[0..maxn]ofinteger; f: array[0..20000]ofinteger; begin readln(maxv,n); fori: =1tondoread(v[i]); fori: =1tondoread(p[i]); fori: =1tondoread(num[i]); k: =0;{组合后的物品个数} fori: =1tondo{对第i件物品进行组合} begin t: =1; whilenum[i]>0do ifnum[i]>=tthenbegin inc(k); v1[k]: =v[i]*t; p1[k]: =p[i]*t; num[i]: =num[i]-t; t: =t*2; end elsebegin inc(k); v1[k]: =v[i]*num[i]; p1[k]: =p[i]*num[i]; num[i]: =0; end; end; v: =v1;p: =p1;n: =k;{更新物品} fillchar(f,sizeof(f),0); best: =0; fori: =1tondo{01背包} forj: =maxvdowntov[i]do begin iff[j-v[i]]+p[i]>f[j]thenf[j]: =f[j-v[i]]+p[i]; iff[j]>bestthenbest: =f[j]; end; writeln(best); end. 3、求恰好装满的情况数 例5、求自然数n(<=1000)本质上不同的质数和的表达式的数目。 输入: 17 输出: 17 输入: 31 输出: 111 输入: 47 输出: 614 输入: 101 输出: 43709 [算法分析] 方法1: 穷举 生成每一个质数的系数的排列,再一一测试。 效率显然很低。 Programex5a; varn,l,now,tot,i,j: longint; xs: array[0..1001]oflongint; pr: array[0..1001]oflongint; flag: boolean; procedurecal(x: longint); vari: longint; begin now: =0; fori: =1toxdo inc(now,xs[i]*pr[i]); end; proceduretry(dep: longint); vari,j: longint; begin cal(dep-1); ifnow>nthenexit; ifdep=l+1then begin cal(dep-1); ifnow=ntheninc(tot); exit; end; fori: =0tondivpr[dep]do begin xs[dep]: =i; try(dep+1); xs[dep]: =0; end; end; begin{main} readln(n); l: =0; fori: =2tondo begin flag: =true; forj: =2totrunc(sqrt(i))do ifimodj=0then begin flag: =false; break; end; ifflagthenbegininc(l);pr[l]: =i;end; end; tot: =0; try (1); writeln(tot); end. 方法2: 递归搜索 Programex5b; varn,l,tot,i,j: longint; pr: array[0..1001]oflongint; flag: boolean; proceduretry(dep,rest: longint); vari,j,x: longint; begin if(rest<=0)or(dep=l+1)then begin ifrest=0theninc(tot); exit; end; fori: =0torestdivpr[dep]do try(dep+1,rest-pr[dep]*i); end; begin{main} readln(n); l: =0;
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 背包 问题