0/1背包
模型:给定 N N N个物品,其中第 i i i个物品的体积为 V i V_i Vi,价值为 W i W_i Wi。有一容积为 M M M的背包,要求选择一些物品放入背包,使得物品总体积不超过 M M M的前题下,物品的价值总和最大。
二维DP
- F [ i , j ] F[i,j] F[i,j]表示从前 i i i个物品中选出了总体积为 j j j的物品,物品最大价值和。
-
F
[
i
,
j
]
=
m
a
x
{
F
[
i
−
1
,
j
]
F
[
i
−
1
,
j
−
V
i
]
+
W
i
}
F[i,j]=max\begin{Bmatrix} F[i-1,j] \\ F[i-1,j-V_i]+W_i \end{Bmatrix}
F[i,j]=max{F[i−1,j]F[i−1,j−Vi]+Wi}
第一种是不选第 i i i个物品,直接继承 i − 1 i-1 i−1.
第二种是选第 i i i个物品,将上一次取物品的价值加上这个物品的价值,且 j ≥ V i j\ge V_i j≥Vi. - 初值:F[0][0]=0,其余为负无穷(防止最大价值本身为0,无法判断是否取物品,能判断F[n][m]是否恰好装满)。
- 目标: m a x { F [ N , j ] } , 0 ≤ j ≤ M max\{ F[N,j]\},0\le j\le M max{F[N,j]},0≤j≤M.
memset(f,0xcf,sizeof(f));//-INF,相当于将符号位改为1;
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++) f[i][j]=f[i-1][j];//从0开始考虑了不取的情况
for(int j=v[i];j<=m;j++) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
//满足了0<=j<=M
}
滚动数组
通过分析代码,发现外层转移只与 i i i和 i − 1 i-1 i−1有关系,所以可以用滚动数组优化空间,降低空间开销。
int f[2][m+1];//外层只开两位,分别是0和1;
memset(f,0xcf,sizeof(f));//-INF,相当于将符号位改为1;
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++) f[i&1][j]=f[(i-1)&1][j];//位运算的奇偶判断,偶0奇1
for(int j=v[i];j<=m;j++) f[i&1][j]=max(f[i&1][j],f[(i-1)&1][j-v[i]]+w[i]);//
}
优化一维DP
我们还能发现:因为 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i−1][j],只做了拷贝的工作,所以f数组的第一维没有存在的必要。
所以代码可以化简为这样
int f[m+1];
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=v[i];j<=m;j++) f[j]=max(f[j],f[j-v[i]]+w[i]);//v[i]~m正序
}
- 这个代码是错误的,因为:假设 f [ j ] f[j] f[j]被 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]]更新,当循环到 f [ j + v [ i ] ] f[j+v[i]] f[j+v[i]]时又会被更新一次,相当于同一个物品被选了多次,这显然违背了0/1背包.
int f[m+1];
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j++) f[j]=max(f[j],f[j-v[i]]+w[i]);//m~v[i]倒序
}
- 这样倒序循环就解决了问题,假设 f [ j ] f[j] f[j]被 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]]更新,当循环到了 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]]会被 f [ j − 2 v [ i ] ] f[j-2v[i]] f[j−2v[i]]更新,保证只选一次。
完全背包
模型:给定 N N N种物品,其中第 i i i种物品的体积为 V i V_i Vi,价值为 W i W_i Wi,并且有无数个。有一容积为 M M M的背包,要求选择若干个物品放入背包,使得物品总体积不超过 M M M的前题下,物品的价值总和最大。
二维DP
-
F [ i , j ] = m a x { F [ i − 1 , j ] F [ i , j − V i ] + W i } F[i,j]=max\begin{Bmatrix} F[i-1,j] \\ F[i,j-V_i]+W_i \end{Bmatrix} F[i,j]=max{F[i−1,j]F[i,j−Vi]+Wi}
选的情况由 i − 1 i-1 i−1变成 i i i,就可以重复选取一个物品。
优化一维DP
-
在刚才的正序代码中我们发现可以重复选取一个物品,这不正是我们要的完全背包,所以;
int f[m+1]; memset(f,0xcf,sizeof(f)); f[0]=0; for(int i=1;i<=n;i++){ for(int j=v[i];j<=m;j++) f[j]=max(f[j],f[j-v[i]]+w[i]);//v[i]~m正序 }
多重背包
模型:给定 N N N种物品,其中第 i i i种物品的体积为 V i V_i Vi,价值为 W i W_i Wi,并且有 C i C_i Ci个。有一容积为 M M M的背包,要求选择若干个物品放入背包,使得物品总体积不超过 M M M的前题下,物品的价值总和最大。
- 思路:拆分,将 C i C_i Ci拆成不同物品再做0/1背包
直接拆分法
-
比较简单,但时间复杂度高,为 O ( M ∗ ∑ i = 1 n C i ) O(M*\sum_{i=1}^{n}C_i) O(M∗∑i=1nCi)。
int f[m+1]; memset(f,0xcf,sizeof(f)); f[0]=0; for(int i=1;i<=n;i++){ for(int k=1;k<=c[i];k++) for(int j=m;j>=v[i];j++) f[j]=max(f[j],f[j-v[i]]+w[i]);//m~v[i]倒序 }
二进制拆分法
分组背包
模型:给定 N N N组物品,其中第 i i i组有 C i C_i Ci个物品。第 i i i组的第 j j j个物品的体积为 V i j V_ij Vij,价值为 W i j W_ij Wij。有一容积为 M M M的背包,要求选择若干个物品放入背包,每组至多选一个,使得物品总体积不超过 M M M的前题下,物品的价值总和最大。
多维费用背包
模型:给定 N N N个物品,其中第 i i i个物品的体积为 V i V_i Vi,重量为 G i G_i Gi,y值为 W i W_i Wi。有一容积为 M M M,载重 H H H的背包,要求选择一些物品放入背包,使得物品总体积不超过 M M M,总重量不超过 H H H的前题下,物品的价值总和最大。