前言——背包问题
让我们先来看一个例子:
有 n n n个物品和一个容量为 W W W的背包,每个物品有重量 w i w_i wi和价值 v i v_i vi两种属性,要求选若干物品放入背包使背包中物品的总价值最大且背包中物品的总重量不超过背包的容量。
在之后的学习中我们还会发现,这种问题还有很多种变式,例如每种物品只能选一次,物品数量有限等。像这种选择物品填充背包并使其总价值最大的问题我们称其为背包问题。
一.01背包
模型特点
以上述引言中了例子为例,如果题目中的物品每种只有一个,状态只有取或者不取,即为 0 0 0或 1 1 1,那么这种问题就是典型的 01 01 01背包问题。
我们现在可以来推导一下这种模型的结论
设 D P DP DP状态 f [ i ] [ j ] f[i][j] f[i][j]为在只能放前 i i i个物品的情况下,容量为 j j j的背包所能达到的最大总价值。
现在我们考虑转移。假设当前已经处理好了前 i − 1 i-1 i−1个物品的所有状态,那么对于第 i i i个物品.
当其不放入背包时,背包的剩余容量不变,背包中物品的总价值也不变,故这种情况的最大价值为 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j] .
当其放入背包时,背包的剩余容量会减小 ,背包中物品的总价值会增大 ,故这种情况的最大价值为 f [ i − 1 ] [ j − w [ i ] ] + v [ i ] f[i-1][j-w[i]]+v[i] f[i−1][j−w[i]]+v[i] 。
z综上所述,我们可以写出它的动态规划表达式:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]) f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i])
由于对 f [ i ] f[i] f[i]有影响的只有 f [ i − 1 ] f[i-1] f[i−1],可以去掉第一维,直接用 f [ j ] f[j] f[j]来表示处理到当前物品时背包容量为 j j j的最大价值,得出以下方程:
f [ j ] = m a x ( f [ j ] , f [ j − w [ i ] ] + v [ i ] ) f[j]=max(f[j],f[j-w[i]]+v[i]) f[j]=max(f[j],f[j−w[i]]+v[i])
务必牢记并理解这个转移方程,因为大部分背包问题的转移方程都是在此基础上推导出来的。
如果不了解背包为什么要01倒推完全顺推的同学(包括我)可以看这一篇文章传送门
例题1采药
#include<bits/stdc++.h>
using namespace std;
int T,M,t[101],v[101],f[1001];
int main()
{
memset(f,0,sizeof(f));
cin>>T>>M;
for(int i=1;i<=M;i++)
cin>>t[i]>>v[i];
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]);
cout<<f[T]<<endl;
return 0;
}
二.完全背包
模型特点
完全背包模型与 01 01 01背包类似,与 01 01 01背包的区别仅在于一个物品可以选取无限次,而非仅能选取一次.
我们可以借鉴 01 01 01背包的思路,进行状态定义:设 f [ i ] [ j ] f[i][j] f[i][j]为只能选前 i i i个物品时,容量为 j j j的背包可以达到的最大价值。
需要注意的是,虽然定义与 01 01 01背包类似,但是其状态转移方程与 01 01 01背包并不相同。
可以发现,对于 f [ i ] [ j ] f[i][j] f[i][j],只要通过 f [ i ] [ j − w [ i ] ] f[i][j-w[i]] f[i][j−w[i]]转移就可以了。因此状态转移方程为:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=max(f[i-1][j],f[i][j-w[i]]+v[i]) f[i][j]=max(f[i−1][j],f[i][j−w[i]]+