有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
模版:
for i=1 to n
{
for j=0 to c[i]-1
f[i][j]=f[i-1][j];
for j=v to c[i]
f[i][j]=max{f[i-1][j],f[i-1][j-c[i]]+w[i]}
}
这里的f[i-1][j]表示的是:不取这件物品,那么就是前i-1个物品在容量为j的最大价值,因为容量是不变的,所以还是j。
f[i-1][j-c[i]]+w[i]表示的是:取这件物品,那么就是上一步的最大价值加上这个重量,因为这一步的容量为j,所以上一步的容量要是j-c[i],那样的话加上这次的重量才是j。
现在我们举一个例子:
#416. 【背包】采药
题目描述
宁智贤是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。” 如果你是宁智贤,你能完成这个任务吗?
输入格式
输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
样例数据
input
70 3
71 100
69 1
1 2
output
3
(PS:之前我打了一个模板,出现了重大重大的失误——我中间少了一个循环,没有复制前面的数据。这是当我打了两千多个字,提交了两遍之后才发现的,太坑了。而且样例数据也太水了。而发现了这个错误之后,可以把第二个j循环换成正序的)
这里先列一下数据:
n=3 有3件物品
v=70 背包的总容量为70
C[1]=71 w[1]=100
C[2]=69 w[2]=1
C[3]=1 w[3]=2
一、i=1时 因为c[1]<v,所以第二个j循环是不存在的,所以
都不变,都为0;f[1][1]到c[1][70]也都为0(复制数据)
(就是第一件物品取与不取的问题,取的话它容量会爆,所以只能不取,值就为0)
二、i=2时 j=70 to 69;f[2][1]到f[2][68]都为0(复制数据)
1.f[2][70]=max(f[1][70],f[1][1]+1)=1
所以说,当上一层都为0的时候,且这个物品可以取,那么就取,值就是它的价值
(就是第二件物品取与不取的问题,因为第一件物品不取,第一件物品的所有的最大价值都为0,且第二件物品不会爆容量,所以取,最大价值为1)
2.f[2][69]=max(f[1][69],f[1][0]+1)=1
(在容量为69的条件下,同上)
三、i=3时 j=70 to 1
1.f[3][70]=max(f[2][70],f[2][69]+2)=3
如果容量为70,那么第二第三件物品都要取
(到这里就有点复杂了,我们可以理解为:假设3不取,
那么最大价值为1;假设3取,且2也能取,那么就两个都取,最大价值为3)
2.f[3][69]=max(f[2][69],f[2][68]+2)=2
如果容量为69,那么只取第三件物品
(同样的思路:假设3不取,还是1;假设3取,在容量
为69的条件下,2是不能取的,因为容量会爆,这时
最大价值为2)
3.f[3][68]=max(f[2][68],f[2][67]+2)=2
(同上,之后都是这样)
……
现在来看一下这个循环的含义:
前1个物品在容量为v的最大价值;
前1个物品在容量为v-1的最大价值;
……
前1个物品在容量为c[1]的最大价值;
前2个物品在容量为v的最大价值;
……
前2个物品在容量为c[2]的最大价值
……
依此类推
(且中间一定要复制数据)
大循环i是枚举从第一个物品到第n个物品,小循环j的第二个循环是每一层循环中算的是前i件物品分别对应每个容量的最大价值。而还有一个循环是用来复制数据的。
(一个主循环i=1..N,每次算出来二维数组f[i][0..V]的所有值。f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]两个子问题递推而来。)
从宏观上看:
j循环因为是倒序的,所以当前面比后面小的时候,说明当前这个物品是绝对不能取的,因为光取它就会爆容量。
当前面比后面大的时候,说明这个物品现在分两种情况:取与不取。所以我们分两步:假设这个物品不取,那么最大价值就是上一步所求出来的;假设它取了,你要判断一下前面的物品还能不能取,如果f[i-1][j-c[i]]为0,就说明它什么物品都没有去取;那么自然它的容量是不会爆的,那么就只取这个物品,那么最大价值就是当前物品的价值;如果值不为0,就说明在前面物品取得条件下,还可以取当前物品。
然后最后输出的就是前n件物品在容量为v下的最大价值。
其实这个板子还可以这样打:
for i=1 to n
for j=v to 0
if j>=c[i] f[i][j]=max{f[i-1][j],f[i-1][j-c[i]]+w[i]}
else f[i][j]=f[i-1][j]
优化一下空间复杂度:
我们思考一下,事实上,这要求在每次主循环中我们以j=V..0
的顺序推f[j],这样才能保证推f[j]时f[j-c[i]]保存的是状态f[i-1][j-c[i]]的值。(这句话是解释了为什么j要倒序:如果将j的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][j]由f[i][j-c[i]]推知,与本题意不符;现在我要的是f[i][j]由f[i-1][j-c[i]]推知。如果是0到v的话,f[i][j]
就是由f[i][j-c[i]]推知,因为f[i][j-c[i]]刚刚被更新过;而如果是v到0的话,f[i][j-c[i]]就还没有更新过,所以用的是上一层所算出来的值所以这样才可以转换为一维)
伪代码如下:
for i=1 to N
for j=V downto c[i]
f[j]=max{f[j],f[j-c[i]]+w[i]}
现在的f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。其实你可以
这么想,原来的二维,它的max里的是i-1,就是上一层所求出
来的。所以这个i-1可以不用,直接转换为一维。
然后这边j不用循环到0的原因是:j-c[i]要大于0才有意义,否则的话f[j]就是等于f[j]。
啊,我思路终于清晰了一点。