> 技术文档 > 动态规划学习---背包问题

动态规划学习---背包问题

2025---3.27,背包问题专练

DP问题的分析我们采用特定的方法

这个分析方法我们可以专门来学习一下

链接:https://www.bilibili.com/video/BV1X741127ZM

 

0-1背包问题

2. 01背包问题 - AcWing题库

由上面的分析我们可以轻松得到0-1背包的状态计算方程

if(j>=w[i])F[i][j]=max(F[i-1][j],F[i-1][j-w[i]]+w[i]);

下面我们对于一个完整的0-1背包问题,给出完整代码,再来讨论从二维优化到一维

#include const int N=1010;using namespace std;int V[N],W[N];//体积和重量int f[N][N];//状态表示int n,v;int main(){ cin>>n>>v; for(int i=1;i>a>>b; V[i]=a; W[i]=b; } for(int i=1;i<=n;i++)for(int j=0;j=V[i])f[i][j]=max(f[i-1][j],f[i-1][j-V[i]]+W[i]);} cout<<f[n][v]; return 0;}

接下来我们考虑一下如何进行优化,我们可以发现,对于 F[i][j],无论是哪种情况都只用到了F[i-1]这一层,因此我们可以从二维化成一维,因为相当于我们每一层的F[j],在对F[j]的数量进行处理之前都是F[i-1][j],所以我们根本不需要存储第一维度的值

简单来说就是,假设现在的i=2,此时我们只有F[j]一列数字,这一列数字其实是,上一轮循环得到的也就是i=1的时候得到的,就是原来二维里面的第i-1层,所以我们只需要在这个基础上进行修改就行,而改成一维怎么改呢

就变成了

F[j]=max(F[j],F[j-V[i]]+W[i])

那么我们要保证的是我们是基于上一层也就是F[i-1][j]的基础上进行修改的,所以应该倒序

假设 j从V[i]开始,到v结束

当j往后移动到时候,前面的值已经改变,就不再是第i-1层的了

所以正确的方式是j从后往前遍历对于 0到V[i]的值没有变化,所以是从v遍历到V[i]

for(int i=1;i=V[i];j--){ f[j]=max(f[j],f[j-V[i]]+W[i]);} 

完全背包问题

在0-1背包的基础上,每个物品的数量不限 

3. 完全背包问题 - AcWing题库

在掌握0-1背包的分析方法后,完全背包问题的分析就很简单了,如图

也就是我们在考虑状态计算的时候,将集合划分成,没有,有一个,有两个....

在此基础上我们的朴素代码可以写成

#include #include using namespace std;const int N=1010;int f[N][N];int v[N],w[N];int n,m;int main(){ cin>>n>>m; for(int i=1;i>v[i]>>w[i]; for(int i=1;i<=n;i++) { for(int j=0;j<=m;j++) { for(int k=0;k*v[i]<=j;k++) { f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]); } } } cout<<f[n][m]; return 0;}

核心在三重循环上,完美对应集合划分

在此基础上我们需要进行优化 

 

核心代码如下

 for(int i=1;i<=n;i++) { for(int j=0;j=v[i])f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]); } }

在此基础上我们是否可以像0-1背包那样优化成一维的呢?,可以,但是思路与0-1背包不同

由公式可得到,f[i][j]并不是全由i-1层得到,相反,我们必须先得到i层元素,还是和原来一样,如果j<v[i]不需要变化,只需要继承上一层,所以我们的遍历应该是从前往后遍历

我们拿0-1背包的图来看看

 

v[i]前面的不用管,j从v[i]开始,当遍历到后面时,j-v已经被修改过了变成了第i层的元素,而正在遍历还没修改的j号元素就是[i-1][j]

所以可以进一步优化为 

 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]); }}

优化为一维数组的完整代码

#include #include using namespace std;const int N=1010;int f[N];int v[N],w[N];int n,m;int main(){ cin>>n>>m; for(int i=1;i>v[i]>>w[i]; 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]); }} cout<<f[m]; return 0;}

多重背包问题

AcWing 4. 多重背包问题 - AcWing

在完全背包的基础上,物品有了个数限制

朴素算法很简单,就是在完全背包的暴力算法基础上进行的

代码仅仅比完全背包的暴力dp多了一个判断

#include #include const int N=110;using namespace std;int s[N],v[N],w[N];int n,m;int f[N][N]; int main(){ cin>>n>>m; for(int i=1;i>v[i]>>w[i]>>s[i]; }  for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) { for(int k=0;k*v[i]<=j&&k<=s[i];k++){ f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k); } } cout<<f[n][m];  return 0;}

 这里我们是不能用f[i][j]和f[i][j-v]之间的关系来简化方程的,因为引入了s[i],所以我们不能像上面那样优化,我们采取优化的方式为二进制优化

二进制优化的多重背包问题

将多重背包问题转化成0-1背包问题

5. 多重背包问题 II - AcWing题库

 我们知道,0-1背包是可以优化到一维的,对于多重背包而言,每个物品有s[i]个,如果从完全背包的角度理解,可以轻松写出暴力代码,但是无法优化三重循环,那如果从0-1背包的角度理解呢,

为什么是这样拆分,而不是直接用二进制呢,假设有50个物品,我们将其装到n个箱子里,要确保的是,我们通过取出不同的箱子组合,得到的值是映射在0到50里的,也就是说我们按照2进制的思想可以映射到 0到2的k次方-1,可以包含1到50但显然无法完全符合条件,我们只需要将最后一个最大的2进制(箱子),转换为剩余的数,比如50

1 2 4 8 16 19

1 2 4 8 16 32

我们需要按照上面的方法进行划分

代码如下

#include using namespace std;int v[12010],w[12010];int cnt=1;int f[2010];int n,m;int main(){ cin>>n>>m; while(n--){ int a,b,c; cin>>a>>b>>c; for(int i=1;i<=c;i<<=1) {  v[cnt]=i*a;  w[cnt]=b*i;  cnt++;  c-=i; } if(c) {  v[cnt]=c*a;  w[cnt++]=c*b; } } for(int i=1;i=v[i];j--) { f[j]=max(f[j],f[j-v[i]]+w[i]); } cout<<f[m];  return 0;}

 注意是<<=或者写成*=2

分组背包问题 

集合划分为选第i组的哪一个物品 

 

#include using namespace std;const int N=110;int n,m;int s[N],v[N][N],w[N][N];int f[N][N];int main(){ cin>>n>>m; for(int i=1;i>s[i]; for(int j=1;j>v[i][j]>>w[i][j]; } }for(int i=1;i<=n;i++)for(int j=0;j<=m;j++){ f[i][j]=f[i-1][j]; for(int x=1;x=v[i][x])f[i][j]=max(f[i][j],f[i-1][j-v[i][x]]+w[i][x]); } }cout<<f[n][m]; return 0;}