背包问题 学习笔记
概述
背包问题是最基本的动态规划模型,大概为:
有 \(n\) 件物品,拿一件物品有 \(v_i\) 的价值和 \(w_i\) 的花费,有一定限制,最多花费 \(W\) 的代价,问价值最多为多少。
简单背包问题
有 \(n\) 件物品,拿一件物品有 \(v_i\) 的价值,问能否从中选取若干件总价值恰好为 \(W\) 。
这里设一个 bool 类型的 DP 数组。设 \(f_{i,j}\) 表示前 \(i\) 件物品能否取到恰好 \(j\),每一个物品有拿或不拿两种选择。
DP 方程:\(f_{i,j}=f_{i-1,j}\operatorname{or}f_{i-1,j-v_i}\)
初始状态:\(f_{i,0}=1\)
答案:\(f_{n,W}\)
此处的空间可以再优化,需要用到 DP 中的常用的压维。
由于 \(i\) 从 \(i-1\) 转移,可以把 \(i\) 压掉,每次循环中继承了上一次的数据。方程变为 \(f_{j}=f_{j}\operatorname{or}f_{j-v_i}\)。
for(int i=1;i<=n;i++)for(int j=W;j>=1;j--)f[j]|=f[j-v[i]];
注意此处内层循环是逆序的。假如写错成正序,那么更新 \(f_j\) 时 \(f_{j-v_i}\) 已经被更新过了,就变成物品可以取无数次了。
0/1背包问题
有 \(n\) 件物品,拿一件物品有 \(v_i\) 的价值,\(w_i\) 的花费,花费不超过 \(W\),每件物品只有一个,求最大价值。
设 \(f_{i,j}\) 为前 \(i\) 件物品,花费 \(j\) 的最大价值。同样有拿或不拿两种选择。同理可以压维。
DP 方程:\(f_{i,j}=\max(f_{i-1,j},f_{i-1.j-w_i}+v_i)\)
答案:\(f_{n,W}\)
for(int i=1;i<=n;i++)for(int j=W;j>=w[i];j--)f[j]=max(f[j],f[j-w[i]]+v[i]);
完全背包问题
有 \(n\) 件物品,拿一件物品有 \(v_i\) 的价值,\(w_i\) 的花费,花费不超过 \(W\),每件物品有无数个,求最大价值。
上面讲到,正序枚举 \(j\),就变成物品可以取无数次了,这正是完全背包问题的解法。
for(int i=1;i<=n;i++)for(int j=1;j<=W;j++)f[j]=max(f[j],f[j-w[i]]+v[i]);
多重背包问题
有 \(n\) 件物品,拿一件物品有 \(v_i\) 的价值,\(w_i\) 的花费,花费不超过 \(W\),每件物品有 \(num_i\) 件,求最大价值。
朴素的做法是把取 \(1\sim num_i\) 件物品当做不同的物品,转化成0/1背包。DP 方程 \(f_{i,j}=\max_{k=0}^{\min(num_i,\lfloor\frac{j}{w_i}\rfloor)}\{f_{i-1,j-w_ik}+v_ik\}\)。
for(int i=1;i<=n;i++)for(int j=W;j>=i;j--)for(int k=1;k<=num[i]&&k*w[i]<=j;k++)f[j]=max(f[j],f[j-k*w[i]]+k*v[i]);
拆分物品件数的过程可以用倍增的思想优化。把 \(num_i\) 拆成 \(1,2,4,8,\dots,2^i\),每个当作一件物品,剩下的也当做一件,这样就用最小的物品数表示了 \(num_i\)。
for(int i=1,t1,t2,t3;i<=n;i++){
cin>>t1>>t2>>t3;
for(int j=1;j<=t3;j<<=1)w[++cnt]=j*t1,v[cnt]=j*t2,t3-=j;
if(t3)w[++cnt]=t3*t1,v[cnt]=t3*t2;
}
for(int i=1;i<=cnt;i++)for(int j=W;j>=w[i];j--)f[j]=max(f[j],f[j-w[i]]+v[i]);
[[动态规划]]

浙公网安备 33010602011771号