0-1背包问题:
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
完全背包问题:
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
可以看出,两种问题的不同是完全背包问题的物品可以无限用,虽然不同,但是他们的模板代码是非常相似。也正因为这样,造成了理解上的困难。
先贴出两种问题的代码:
0-1背包问题:
void dp(int *f, int *t, int *v, int M, int T)
{
for (int i = 1; i <= M; i++)
{
for (int j = T; j >= t[i]; j--)
{
f[j] = max(f[j - t[i]] + v[i], f[j]);
}
}
}
完全背包:
void dp(int *f, int *t, int *v, int M, int T)
{
for (int i = 1; i <= M; i++)
{
for (int j = t[i]; j <= T; j++)
{
f[j] = max(f[j - t[i]] + v[i], f[j]);
}
}
}
可以看到代码的不同之处就是 j 的遍历是正序还是逆序。下面解释一下:
0-1背包问题:特点:每种物品仅有一件,可以选择放或不放。
根据状态转移方程:第 i 次决策取决于第 i - 1次决策。这是 j 逆序遍历的原因。
完全背包问题:特点:每种物品有无限件。
但是,决策和0-1背包问题不一样:第 i 次的决策不一定取决于第 i -1 次。当第i件物品出现时,可能会对以前的状态造成影响。因为有可能第i件物品出现后,之前所确定的状态加以改变 再加上第i件物品放入会构造更好的解决方案。所以,之前的状态需要改变,故要正序遍历 j 值。
举个例子:
这里有两道OJ题目:
0-1背包问题:https://www.luogu.org/problemnew/show/P1048
完全背包问题:https://www.luogu.org/problemnew/show/P1616
就以题目中的数据为例:
设总时间70,采药总数3。
采药所需时间和价值分别为:71,100; 69, 1; 1, 2.
- 0-1背包问题:第一次决策要选择69 1;第二次则在第一次的决策上选择1 2;我们不需要去改变第一次的选择。
- 完全背包问题:第一次决策可以选69 1,且只能选择一株。然后,我们进行第二次决策选择,此时可以且只能再选择一株1 2,这样构造的最终结果为f[T] = 3。
那么,我们可不可以构造比f[T] = 3更大的解呢?此题当然有。就是第一次决策选择1 2;然后以后的决策都选择1 2;这样最终结果f[T] = 140。比 f[T] = 3大多了。
由此可以看出,我们在得到最优解过程中,是在进行第二次决策时否定了第一次的决策,需要重新进行以前的决策。这就是V正序遍历的原因。
再解释这个正序遍历的代码意思:
i 表示第几件物品;
j 表示当前背包的容量;
在遍历到每一个 i (即每个物品)时,都要正序重新遍历一遍 j (即背包容量),也就是说更新以前(上一件物品时)的 f[j]。正好符合完全背包问题的决策思想。
最后,如果理解有错误还请多多指正。