背包问题.docx

上传人:b****7 文档编号:9542960 上传时间:2023-02-05 格式:DOCX 页数:31 大小:27.48KB
下载 相关 举报
背包问题.docx_第1页
第1页 / 共31页
背包问题.docx_第2页
第2页 / 共31页
背包问题.docx_第3页
第3页 / 共31页
背包问题.docx_第4页
第4页 / 共31页
背包问题.docx_第5页
第5页 / 共31页
点击查看更多>>
下载资源
资源描述

背包问题.docx

《背包问题.docx》由会员分享,可在线阅读,更多相关《背包问题.docx(31页珍藏版)》请在冰豆网上搜索。

背包问题.docx

背包问题

背包问题

常州一中林厚从

 

背包问题是信息学奥赛中的经典问题。

背包问题可以分为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模板 > 其它模板

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1