写在前面
由于本人实力尚浅,接触算法没多久,写这篇blog仅仅是想要提升自己对算法的理解,如果各位读者发现什么错误,恳请指正,希望和大家一起进步。(●’◡’●)
完全背包问题
了解完全背包问题前可以先去看看01背包问题(良心正解),先了解这个基础问题会更有利于你了解下面的完全背包问题(个人观点)
题目
思路
重要变量说明:
f[][[]
:用于状态表示;w[]
:记录每个物品的价值;v[]
:记录每个物品的体积
- 定义二维数组
f[][]
,其中f[i][j]
表示在前i
个物品,背包容积为j
的限制下所能装下的最大价值。这里的f[i][j]
就是做法的集合,f[i][j]
的值就是最大价值即属性。 - 从
i=1
开始枚举,对于第i
个物品,都有无数种选择(看似是无数种,其实还是有限制的):- 如果不选第
i
个物品,那么状态转移方程为f[i][j]=f[i-1][j]
- 如果选择第
i
个物品一次,那么状态转移方程为f[i][j]=f[i-1][j-v[i]]+w[i]
- 如果选择第
i
个物品二次,那么状态转移方程为f[i][j]=f[i-1][j-2*v[i]]+2*w[i]
......
- 如果选择第
i
个物品k
次,那么状态转移方程为f[i][j]=f[i-1][j-k*v[i]]+k*w[i]
- 如果不选第
- 我们因为要求最大价值,所以对上面两种情况去
max
即可
我们发现其实上面的思路大致上和01背包问题差不多,只不过对于每一个物品
i
,我们不止两种选择(选与不选)而是有无数种选择。所以我们可以在01背包问题的基础上,在两层循环中再套一层循环,表示选择第i
个物品多少次。
代码(不优化版,二维数组)
#include<iostream>
using namespace std;
const int N=1010;
int f[N][N],v[N],w[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++) //i表示当前选择到第i个物品,我们从第1个物品开始枚举,一直到n个物品
for(int j=1;j<=m;j++) //j表示当前背包的容积,我们从1开始枚举,一直到背包的最大的容积
for(int k=0;k<=j/v[i];k++) //k表示当前的选择第i个物品的次数,一直到所能选择的最大次数
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
printf("%d\n",f[n][m]);
return 0;
}
优化1(降低时间复杂度)
我们可以看到上面的思路虽然可以求解问题,但是时间复杂度是 O ( l o g 2 n ) O(log_2n) O(log2n),而题目给出的数据是 1 0 3 10^3 103,那么最后就是 1 0 9 10^9 109,而我们的要求是1s内,大概是 1 0 7 − 1 0 8 10^7-10^8 107−108之间,所以我们还要优化
思路
f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可得出如下递推关系:
f[i][j]=max(f[i,j-v]+w , f[i-1][j])
还是没看懂的宝宝们可以可以看下面这份详细推理
代码
#include<iostream>
using namespace std;
const int N=1010;
int f[N][N],v[N],w[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j>=v[i])
f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
else
f[i][j]=f[i-1][j];
}
printf("%d\n",f[n][m]);
return 0;
}
思考
我们把完全背包问题和01背包问题做一下对比:
01背包问题:f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
完全背包问题: f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
于是乎,聪明的你一定想到了完全背包问题的代码还可以优化,只使用一维数组,降低空间复杂度
优化2(降低空间复杂度)
具体为什么可以只用一维数组请看01背包问题
代码
#include<iostream>
using namespace std;
const int N=1010;
int f[N],v[N],w[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j>=v[i])
f[j]=max(f[j],f[j-v[i]]+w[i]);
else
f[j]=f[j];
}
printf("%d\n",f[m]);
return 0;
}
再思考
Q:为什么01背包问题使用一维数组时,枚举背包空间时要逆序,而完全背包问题使用一维数组时,枚举背包空间时是正序?
A:01背包问题中我们每次更新用的都应该是上一层即i-1
层的数据,但是如果正序枚举背包空间j
的话,在更新较大容积时,用到的就是已经污染的数据,即在第i
层时,可能在j=v[i]
时选了i
这个物品,当j=2*v[i]
时我们可能又选了i
这个物品,而第i
这个物品只能被选一次,而逆序枚举时,每次都只可能选择第i
这个物品一次。但是在完全背包问题中我们就要用正序,因为我们要的就是已经更新的数据,因为每个物品都有无数个,所以可以选择第i
个物品多次
如果你觉得我写题解还不错的,请各位王子公主移步到我的其他题解看看
数据结构与算法部分(还在更新中):
C++ STL总结 - 基于算法竞赛(强力推荐)
最短路算法——Dijkstra(C++实现)
最短路算法———Bellman_Ford算法(C++实现)
最短路算法———SPFA算法(C++实现)
最小生成树算法———prim算法(C++实现)
最小生成树算法———Kruskal算法(C++实现)
染色法判断二分图(C++实现)
动态规划——01背包问题
Linux部分(还在更新中):
Linux学习之初识Linux
Linux学习之命令行基础操作
✨🎉总结
“种一颗树最好的是十年前,其次就是现在”
所以,
“让我们一起努力吧,去奔赴更高更远的山海”
如果有错误❌,欢迎指正哟😋文章来源:https://www.toymoban.com/news/detail-762112.html
🎉如果觉得收获满满,可以动动小手,点点赞👍,支持一下哟🎉文章来源地址https://www.toymoban.com/news/detail-762112.html
到了这里,关于动态规划——完全背包问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!