动态规划经典案例详解之背包问题【摘要】本文主要从动态规划经典案例——背包问题的动态规划设计思路出发,结合具体实例,对动态规划在程序设计中的典型应用以及衍生拓展进行详细分析。
【关键字】动态规划信息学奥赛0/1背包问题动态规划并非一个算法,而是一种解题的思路,其核心思想是通过使用大量的存储空间把中间结果记录下来,大大减少重复计算的时间,从而提高的程序的执行效率,因为信息学奥林匹克复赛题目的解决程序一般是有时间限制的,对于某些用搜索必然耗费大量时间的题目,动态规划几乎是唯一的选择。
但是动态规划并没有一个简单的模型可以套用,对于每个不同的题目都有对应的不同规划思路,我们只能通过对一些动态规划经典案例的学习来训练自己的动态规划思维能力,从而以不变应万变,应付各种复杂的程序设计,本文通过对动态规划经典案例之一的背包问题进行详细阐述,旨在让学生了解动态规划和搜索的不同设计思路以及动态规划的优越性。
【原型例题】从n个物品中选取装入背包的物品,每件物品i的重量为wi,价值为pi。
求使物品价值最高的选取方法。
【输入文件】第一行一个数c,为背包容量。
第二行一个数n,为物品数量第三行n个数,以空格间隔,为n个物品的重量第四行n个数,以空格间隔,为n个物品的价值【输出文件】能取得的最大价值。
【分析】初看这类问题,第一个想到的会是贪心,但是贪心法却无法保证一定能得到最优解,看以下实例:贪心准则1:从剩余的物品中,选出可以装入背包的价值最大的物品,利用这种规则,价值最大的物品首先被装入(假设有足够容量),然后是下一个价值最大的物品,如此继续下去。
这种策略不能保证得到最优解。
例如,考虑n=2,w=[100,10,10],p=[20,15,15],c=105。
当利用价值贪婪准则时,获得的解为x=[1,0,0],这种方案的总价值为20。
而最优解为[0,1,1],其总价值为30。
贪心准则2:从剩下的物品中选择可装入背包的重量最小的物品。
虽然这种规则对于前面的例子能产生最优解,但在一般情况下则不一定能得到最优解。
考虑n=2,w=[10,20], p=[5,100],c=25。
当利用重量贪婪策略时,获得的解为x=[1,0],比最优解[0,1]要差。
贪心准则3:价值密度pi/wi贪婪算法,这种选择准则为:从剩余物品中选择可装入包的pi/wi值最大的物品,但是这种策略也不能保证得到最优解。
利用此策略解n=3,w=[20,15,15],p=[40,25,25],c=30时的得到的就不是最优解。
由以上的三种贪心策略可知,本题如果采用贪心方法求解,则完全取决于输入的数据,不管采用哪种方法都不能保证完全正确。
既然贪心不能解决,那么搜索行不行呢?我们可以深度搜索每种取物方案,然后依次对比得到的最终结果,取最大值即可。
这个思路是正确的,结果也是可期的,但是时间代价是阶乘级的,当物品数量很多(N>10就已经需要很长时间了)时,所耗费的时间代价是巨大的,对于奥赛要求一秒钟内出解就根本不可能了,于是我们不得不想另外的思路,【新思路】要使物品价值最高,即p1*x1+p2*x1+...+pi*xi(其1<=i<=n,x取0或1,取1表示选取物品i)取得最大值。
在该问题中需要决定x1..xn的值。
假设按i=1,2,...,n的次序来确定xi的值。
如果置x1=0,则问题转变为相对于其余物品(即物品2,3,.,n),背包容量仍为c的背包问题。
若置x1=1,问题就变为关于最大背包容量为c-w1的问题。
现设r={c,c-w1}为剩余的背包容量。
在第一次决策之后,剩下的问题便是考虑背包容量为r时的决策。
不管x1是0或是1,[x2,.,xn]必须是第一次决策之后的一个最优方案。
也就是说在此问题中,最优决策序列由最优决策子序列组成。
这样就满足了动态规划的程序设计条件。
【动态规划思路】阶段i:在前i件物品中,选取若干件物品放入背包中;状态:在前i件物品中,选取若干件物品放入所剩空间为c的背包中的所能获得的最大价值;决策:第i件物品放或者不放;由此可以写出动态转移方程:用f[i,j]表示在前i件物品中选择若干件放在所剩空间为j的背包里所能获得的最大价值f[i,j]=max{f[i-1,j-wi]+pi(j>=wi),f[i-1,j]}这样,就可以自底向上地得出在前n件物品中取出若干件放进背包能获得的最大价值,也就是f[n,c]【算法伪代码】for i:=0to c do{i=0也就是没有物品时清零}f[0,i]:=0;for i:=1to n do{枚举n件物品}for j:=0to c do{枚举所有的装入情况}beginf[i,j]:=f[i-1,j];{先让本次装入结果等于上次结果}if(j>=w[i])and(f[i-1,j-w[i]]+p[i]>f[i,j]){如果能装第i件物品}then f[i,j]:=f[i-1,j-w[i]]+p[i];{且装入后价值变大则装入}end;writeln(f[n,c]);【输入文件】104514340102530【输出结果】下面列出所有的f[i,j]0000404040404040101010104050505050501010102540505050657510103040405055708080分析次结果,可以很清楚的了解整个程序的执行过程,最后的80就是本题的答案。
【实例1:开心的金明(NOIP2006普及组真题)】金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。
今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N元。
于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。
他还从因特网上查到了每件物品的价格(都是整数元)。
他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1...jk,则所求的总和为:v[j1]*w[j1]+..+v[jk]*w[jk]请你帮助金明设计一个满足要求的购物单。
【输入文件】输入的第1行,为两个正整数,用一个空格隔开:N m(其中N(<30000)表示总钱数,m(<25)为希望购买物品的个数。
)从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有2个非负整数v p(其中v表示该物品的价格(v≤10000),p表示该物品的重要度(1~5))【输出文件】输出只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<100000000)。
【分析】掌握了背包原型题目,此题可以迎刃而解,只要把总钱数N元看成是背包容量,物品的价格看成是物品的重量,重要度看成价值即可直接套用背包原型程序。
【实例2:金明的预算方案(NOIP2006提高组真题)】【题目描述】金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。
今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:主件附件电脑打印机,扫描仪书柜图书书桌台灯,文具工作椅无如果要买归类为附件的物品,必须先买该附件所属的主件。
每个主件可以有0个、1个或2个附件。
附件不再有从属于自己的附件。
金明想买的东西很多,肯定会超过妈妈限定的N元。
于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。
他还从因特网上查到了每件物品的价格(都是10元的整数倍)。
他希望在不超过N 元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,……,jk,则所求的总和为:v[j1]*w[j1]+v[j2]*w[j2]+…+v[jk]*w[jk]。
(其中*为乘号)请你帮助金明设计一个满足要求的购物单。
【输入格式】输入文件的第1行,为两个正整数,用一个空格隔开:N m其中N(<32000)表示总钱数,m(<60)为希望购买物品的个数。
)从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数v p q(其中v表示该物品的价格(v<10000),p表示该物品的重要度(1~5),q表示该物品是主件还是附件。
如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号)【输出格式】输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。
【输入样例】100058002040051300514003050020【输出样例】2200【分析】本题跟上题非常类似,可以确认用背包问题可以解决,但是难度却大大高于上题,这里提供两个思路。
简单方案:对每一个物品做两种决策,取与不取。
如果取,满足两个条件:1.要么它是主件,要么它所属的主件已经在包里了。
2.放进去后的重要度与价格的成绩的总和要比没放进时的大。
这两个条件缺一不可的。
于是得到如下的动规方程:f[i,j]:=f[i-1,j];if(i为主件or i的主件在包中)and(f[i,j]<f[i,j-v]+v*w)then f[i,j]:=f[i,j-v]+v*w;这个方案看似简单,其实有个非常复杂的问题,就是后一个条件“i的主件在包中”的判断,因为动态规划有个固有的弱点,就是很难知道整个中间过程,所以这个判断其实写起来是非常麻烦的。
下面提供一个更好的方案:改进方案:细细的看题目,还一个很重要的条件我们还没用:“每个主件可以有0个,1个或2个附件”。
也就是说对于一套物品(包含主件,所有的附件),我们称为一个属类,对一个属类的物品的购买方法,有以下5种:1.一个都不买2.主件3.主件+附件14.主件+附件25.主件+附件1+附件2这五种购买方法也是唯一的五种方法,也就是说对一属类的物品,我们只有上述的5种购买方法。
于是我们很自然的就会想到把物品按物品的属类捆在一起考虑。
这样我们把物品的属类作为dp的状态。
可以得到如下的dp方程:f[i,j]=max{f[i-1,j];第1种情况f[i-1,j-v[i,0]]+v[i,0]*w[i,0];第2种情况f[i-1,j-v[i,0]-v[i,1]]+v[i,0]*w[i,0]+v[i,1]*w[i,1];第3种情况f[i-1,j-v[i,0]-v[i,2]]+v[i,0]*w[i,0]+v[i,2]*w[i,2];第4种情况f[i-1,j-v[i,0]-v[i,1]-v[i,2]]+v[i,0]*w[i,0]+v[i,1]*w[i,1]+v[i,2]*w[i,2];}第5种情况这种方法的DP效率大大提高,不过需要对输入数据进行重新处理,使之按属类重新编号。