【滨小之旅背包】01背包再复习

引入情景:

为了迎合凡人热烈的需求,荒天集团推出了n种不同体积不同功效的长生丹药(第 i i i种丹药的体积为 v i v_i vi,功效为 w i w_i wi),长生丹药由于其独一无二的珍贵性,每种丹药只有一颗(吃了就可以和HTD一样获得长生哦!),而你作为荒教最优秀的扫地僧,此时忍不住向被打落十八层地狱的前同事lzj学习坏事!你准备了一个体积为 V V V的背包,试图偷走HTD费尽心血熬出来的长生丹药!
被恶魔lzj带坏的你,现在想知道你最多能带走多少功效的丹药!


常规二维方法

我们设 f [ i ] [ j ] f[i][j] f[i][j]表示用前i种丹药,体积为j时获得的最大功效
如何进行状态转移呢?
对于第 i i i种丹药,我们有两种选择:
1、不选择第 i i i种丹药,此时 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i-1][j] f[i][j]=f[i1][j]
2、选择第i种丹药,此时 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − v i ] + w i ) f[i][j]=max(f[i-1][j-v_i]+w_i) f[i][j]=max(f[i1][jvi]+wi)

由于对于每一种丹药,只有用和不用两种选择,所以这种背包也叫做 01 背包 01背包 01背包

对于二维的01背包,由于好理解,我们不再赘述


再观察

斯~~
好像更新第i个物品的01背包的时候
他只跟 f [ i − 1 ] f[i-1] f[i1]的状态有关?
1 1 1~ i − 2 i-2 i2之间的状态好像无关紧要?
那这样为此定义二维是不是太浪费空间了?
没错,我们其实只需要再开一个数组 g [ i ] g[i] g[i]记录 f [ i − 1 ] f[i-1] f[i1]的状态
更新的时候只需要从 g g g数组中更新即可
例如上面的问题
我们可以用如下代码解决:

	g[0] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = a[j]; j <= m; j++) f[j]=max(g[j],g[j-v[i]]+w[i]];
		for (int j = a[j]; j <= m; j++) g[j] = f[j];
	}

灵光一现

可是,我觉得再开一个g数组也太麻烦了
能不能只用一个 f f f数组去解决问题了
于是我们尝试去掉 g g g数组,把 g g g数组直接换成 f f f数组

	g[0] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = a[i]; j <= w; j++) f[j]=max(f[j],f[j-v[i]]+w[i]];
	}

得到的结果让人大失所望:
不对啦!
结果怎么比我预期的大好多呢!

对于这种结果,我们尝试思考原因
对于这道题而言,结果比预期大,只有一种原因:
那就是每种丹药我们用了不止一次
有一些丹药我们重复用了!
而题目中丹药的限制是每种丹药一种
这就可能导致我们的结果比预期要大很多

但是,为什么呢?
我们尝试从程序本身入手:
假设目前体积为 j j j时我们的 f [ j ] f[j] f[j]成功从 f [ j − v [ i ] ] f[j-v[i]] f[jv[i]]处更新过来了
好,这就意味着此时这个丹药我们已经用了,按道理来说我们已经不能再用了
但是,对于这个程序而言
f [ j + v [ i ] ] = f [ j ] + w [ i ] f[j+v[i]]=f[j]+w[i] f[j+v[i]]=f[j]+w[i]
此时我们仍然可以用这颗丹药去更新 f [ j + v [ i ] ] f[j+v[i]] f[j+v[i]]的状态
那这个丹药不是被用了两次呢?
同理 f [ j + 2 ∗ v [ i ] ] , f [ j + 3 ∗ v [ i ] ] … … f[j+2*v[i]],f[j+3*v[i]]…… f[j+2v[i]],f[j+3v[i]]……都可以用这颗丹药去更新
这样一来,这颗丹药不是变成了无限多了吗!
叮咚~
恭喜你解锁新篇章——完全背包

没错,我们终于搞懂了原因
如果是这样顺序循环的话,一颗丹药的受益是可以不断被叠加的
这样就导致了上面的结果——一颗丹药被用了无数次!
这个背包,叫做完全背包,而不是01背包

那要怎么做,才能保证一颗丹药只能被用到一次呢?

我们剖析出现上述问题的根本:
之所以我们会出现一颗丹药被用到无数次的困境,是因为我们用当前状态下已经更新过的状态去更新下面还未更新过的状态
这样就会导致一颗丹药不断被滚雪球,越滚越大
如何才能导致已经更新的状态不会对接下来的状态造成影响呢?
没错
从后往前更新
我们尝试将第二重循环倒置一下呢?

	g[0] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = m; j >= a[i]; j--) f[j]=max(f[j],f[j-v[i]]+w[i]];
	}

这样一来,已经被更新的状态是后面的状态
而目前需要被更新的状态只跟前面的状态有关
这样就完美错开了已经更新过的和需要被更新的之间的关系
在这一层中这一颗丹药也只被用到了一次


习题

真正优秀的商人需要学会为自己打广告

你可能感兴趣的:(滨小之旅,算法,背包)