赞
踩
由于本人实力尚浅,接触算法没多久,写这篇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; }
我们可以看到上面的思路虽然可以求解问题,但是时间复杂度是 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]);
于是乎,聪明的你一定想到了完全背包问题的代码还可以优化,只使用一维数组,降低空间复杂度
具体为什么可以只用一维数组请看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学习之命令行基础操作
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。