完全背包问题----思想的理解

来源:互联网 发布:淘宝推广方式 编辑:程序博客网 时间:2024/06/05 00:52

最近学习动态规划,在背包问题处被卡了很久,故在此处把这几天的遇到的问题和自己的理解进行一下总结。


这篇博文主要介绍的是完全背包的思想,包括它的三种状态转移方程。作为引入完全背包问题的前提,附带的介绍了DAG思想和01背包问题。

参考资料有《算法竞赛入门经典》和《背包九讲》。


1.在《算法竞赛入门经典》中,其对完全背包问题是通过DAG的思想来讨论的。先讨论了完全背包问题,再推广到01背包问题。

2.在《背包九讲》中,讨论顺序相反,其对完全背包的讨论是通过01背包的思想推广而来。


一、先来理解《算法竞赛入门经典》的思想。

在看完全背包问题时先来看一个硬币问题。描述如下:

有n中硬币,每种硬币的面值为v1,v1......vn.且每种硬币有无限多。现在给定一个非负整数S,可以选用多少个硬币,使得面值之和恰好为S。输出硬币数目的最小值和最大值。


分析:如何通过建图求解

把要凑足的总面值看成一个个点。起点为S,终点为0. 

那么什么样的两个点是联通的呢?很显然点i到点i-vj(i>=vi)是联通的。取硬币j,状态就由i转移到了i-vj,点i与i-vj联通说面这两点之间有一条线。

很显然,从点i出发的线可能是0条,也可能不止一条。

我们现在的问题就变成了从点S到点0,最长的路径和最短的路径。


定义的d[i]为面值之和为i时最大的硬币数目。则有:

d[i] = max(d[i-v[j]]+1)   j : 0->n  且   i-v[j]>=0

这个方程叫做状态转移方程。

那么我们是如何来考虑这个状态转移的过程的呢?

即在图中,我们是如何决定,在所有与i联通的点中,选择哪一个点。很显然应该取使d[i-v[j]]最大的那个j.


伪代码如下

(是从硬币的面值总额来考虑这个问题)

d[0] = 0

for j:1->S  d[j] =-INF    //因为此题要求恰好取到S,要用一个特殊值去表示不能取到的值,否则的话里面是数组随意赋予的值

for  i:1->S

    do  for j:1->n

            if(v[j]<=i && d[i]<d[i-v[j]]+1)d[i] = d[i-v[j]]+1;  



PA:注意此处两层循环的顺序,第一层循环是对总价值的循环,第二次循环是对硬币种类的循环。《背包九讲》中会把两层循环的顺序改变。


有这个问题来看待完全背包问题,问题描述:

有n种背包,每个包有体积vj重量wj两个属性,每种背包有无穷多个。现有体积为C的大包,问如何装能使大包的重量最大。


分析:

多了一个重量属性,想当于边上有了权值,之前权值为1,现在权值为Wj

定义d[i]为装满体积为i的包是的最大重量

d[i] = max{d[i-v[j]]+w[j]}


二、接着来理解《背包九讲》中的思想

先来看一下01背包问题,描述如下:

有n种背包,每个包有体积vj重量wj两个属性,每种背包只有一个。现有体积为C的大包,问如何装能使大包的重量最大。


这个时候我们重新来定义一下状态转移方程。

d[i][j] 表示取前i种包装到体积为j的包中时所能达到的最大重量,则有:

d[i][j] =max{ d[i-1][j],d[i-1][j-c[i]]+w[i]}

其实这个转移方程也很好理解,无非就是有i-1种包转移到i种包时,第i个包到底取不取的问题。

伪代码如下:

d[0][i] =0;

d[i][0] =0;

for  i:1->n

    do  for  j:c[i]->C

          d[i][j] = max{d[i-1][j],d[i-1][j-c[i]]+w[i]}


对以上代码可以把的d优化为一维的数组

伪代码如下:

d[0....C] =0;

for  i:1->n

   do   for  j:C->c[i]          (注意j历遍的顺序变了,上面的伪代码是由c[i]->C,而这里的伪代码是由C->c[i])

        d[j] = d[j] >d[j-c[i]]]+w[i]?d[j]:d[j-c[i]]]+w[i]


如何由一维数组到二维数组的呢?

在二维数组的情况中,对于i的每一个值,都能算出一个d[i,0.....C]与其对应。那么,能否保证第i次循环后,d[v]是d[i][v]的值呢?

d[i][v]是由d[i-1][v],d[i-1][v-c[i]]+w[i]递推得到的.所以要保证在第i次循环的时候,d[v]中的值是d[i-1,v]的值。

j变为逆序就可以满足上述条件。(如果难以理解,最好上机调试单步查看结果)


也可以这么理解,第i次循环,d[v]的更新由上一次的d[v]和d[v-c[i]]中的值来决定,所以d[v]要在d[v-c[i]]前更新。

如果是由c[i]到C的顺序执行,则d[v-c[i]]一定会在d[v]前面更新,这是更新d[v]时d[v-c[i]]中的值已经是d[i,v-c[i]],而不是d[i-1,v-c[i]]了。


由01背包问题到完全背包问题:

首先看二维的方式,二维的时候很好定义状态转移方程

d[i][j]仍是前i种背包装入到j体积中时的最大重量,那么

d[i][j] = max{d[i][j-k*c[i]]+k*c[i]},    k:0->j/c[i]


同样二维的可以简化为一

伪代码如下:

d[0....C] =0;

for  i:1->n

   do   for  j:c[i]->C         (注意此时j的历遍顺序恰好和01背包相反,而此时两个for循环的顺序与《算法竞赛入门》中for循环的顺序恰好相反,

                                       《背包九讲》里也提到两个for循环的顺序可以互换,就在此处体现。要注意不是任何情况是两个for循环都可以替换,

                                   如UVA674硬币找零问题, 后面会有专门的文章总结。)

        d[j] = d[j] >d[j-c[i]]]+w[i]?d[j]:d[j-c[i]]]+w[i]



1 0