背包问题 学习笔记

概述

背包问题是最基本的动态规划模型,大概为:

\(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]);

[[动态规划]]

posted @ 2024-03-01 09:21  lgh_2009  阅读(5)  评论(0)    收藏  举报