Bootstrap

对基础背包的公式理解

最近开始跟随大佬的脚步学习acm,在学习的过程才发现编程不是那么地简单的,学好编程一定要有好的数学功底才行。最近接触到动态规划,然后最基础的背包公式就难住了我很久,为此我翻遍了很多大佬的博客,在网上搜索了很久再加上自己的顿悟,才有了胆量在此写下这一篇博客。
本博客旨在理解公式,并不会过多的讲解背包其他问题,毕竟我也只是个刚触及背包的菜鸟而已。
下面开始讲解一下基础背包问题。

问题描述
有N件物品和一个容量为V的背包。第i件物品的重量是wi],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。对于这是一个动态规划题,所以解这种题就需要一个状态转移公式,此题公式如下:

f[i][j] = max( f[i-1][j],  f[i-1][j-w[i]] + v[i] )

w[i]与v[i]题目中有提到,后面都不详细解释了

首先要对f[i][j]这个子问题状态定义要理解透,即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。

那么公式右边第一个f[i-1][j]就是表示表示前i-1件物品恰放入一个容量为v的背包可以获得的最大价值。

为什么f[i][j]有可能等于f[i-1][j]?

第i件物品可以选择放入或者不放入包中,也就是说f[i-1][j]是指不放第i件物品放到此时包里,那么f[i-1][j-w[i]]就是理解成第i件物品要放到包里了

j是i的状态下的背包容量,那么i-1下的状态应该是不放i物的状态,即j-weight[i]。又问前面f[i-1][j]中为什么是j而不是(j-weight[i])呢?

上面说过f[i-1][j]这个表示不放第i件物品放到包里, 那么i-1状态 到 i状态下,背包的容量不变,假设i-1下的容量是v而i下的是j,原本应该这样子写f[i-1][v],但现在只知道j不知道v而又j= v,故写成了f[i-1][j]。

误区在j上,f[i-1][j]与f[i-1][j-w[i]]中的j,值看似相同,但是意义就大变了。

公式代码


for(int i = 1; i <= n; i++)  
{  
    for(int j = 0; j <= V; j++)  
    {  
        if(j < w[i])    dp[i][j]  = dp[i-1][j];  
        else dp[i][j] =  max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]); 
    }  
}  

还可以简化,第二个for中,j初始值为w[i]即可,自己想想试试吧。

现在为了简化,f 用一维数组表示 f[j], 直接给出伪代码了,主要是讲理解,其他内容自己翻大佬们的吧。
for i=1..N
    for j=V..0
        f[j]=max{f[j],f[j-w[i]]+v[i]};

我当初卡死在v为什么要逆序这一块,然后我看了一个大佬的博客才明悟了。下面讲得很啰嗦了,因为是当初理解时,一时兴起写下来的,我也没去改动了。

首先记住f[j]现在是临时存值的,因为每次变动i时都会用到v次(姑且理解为用到v次吧,因为j>w[i]这个条件有的用不到v次,但为了方便理解就这样说了)f数组在每一轮时都在不断更新它的值,但由于j,所以是逆序访问f数组的,f数组是有f[v]到f[0]顺序换值的,说这么多废话干啥呢?

呵呵,快接近答案了!

当初在i状态时, 还没更新的那些f数组仍然保留在i-1状态下的值是吧!(这句好好体会,应该不难的)。
而f[j] = max{f[j], f[j-w[i]]+v[i]}这个公式是(状态下的f[]值)<—-(推出)—-(i-1状态时的f[]值)。
即,用还没有更新值、还处于i-1状态下的靠前的f[]来更新后面的f[]的值,这样就保证了f[]的值是由i-1状态推出i状态。

那么值必然是从f[0]—->f[v]一路更新下去,而因为公式可知,f[]的值是由前面的f[]的值推出来的,而前面的f[]值早就更新到第i状态了,你现在是拿i状态下的值推i状态下的值,呵呵,不对了吧!

好了我就是这样理解了这两个最基本的背包公式,理解了公式,对后面几个背包的算法有很大帮助。

写到这里我也犯晕了,没有当初初次理解背包公式的那种神秘的感觉了。不过还没理解的可以再看几遍,或者翻翻其他大佬的博客看看,自己体会一下哈。

在网上翻了很多,但发现很多是直接把一段晦涩难懂的话复制粘贴的,搞得头晕。
我看到客一篇对公式理解很好的博客,讲得特别好,博客传送之门

;