最近开始跟随大佬的脚步学习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状态下的值,呵呵,不对了吧!
好了我就是这样理解了这两个最基本的背包公式,理解了公式,对后面几个背包的算法有很大帮助。
写到这里我也犯晕了,没有当初初次理解背包公式的那种神秘的感觉了。不过还没理解的可以再看几遍,或者翻翻其他大佬的博客看看,自己体会一下哈。
在网上翻了很多,但发现很多是直接把一段晦涩难懂的话复制粘贴的,搞得头晕。
我看到客一篇对公式理解很好的博客,讲得特别好,博客传送之门