背包问题是动态规划中的经典问题,而01背包问题是最基本的背包问题,也是最需要深刻理解的,否则何谈复杂的背包问题。
POJ3624是一道纯粹的01背包问题,在此,加入新的要求:输出放入物品的方案。
我们的数组基于这样一种假设:
totalN表示物品的种类,totalW表示背包的容量
w[i]表示第i件物品的重量,d[i]表示第i件物品的价值。
F(i,j)表示前i件物品放入容量为j的背包中,背包内物品的最大价值。
F(i,j) = max{ F(i-1,j) , F(i-1,j-w[i])+d[i] }
我们仅考虑第i件物品到底放不放进背包
第一项表示不放入背包的情况。
第二项表示放入背包的情况。
然后,我们为了获得具体的方案,每当在局部最优的方案成立时,则将path[i][j]置为1,当求的最优结果后,从最终结果开始回溯,看看在第i轮的局部最优中是否放入物品i。
此时的代码如下所示:
#include <stdio.h> #include <string.h> #include <string> #include <iostream> using namespace std; #define max(a,b) a>b?a:b; //数组要设的比给的范围稍大一些 int dp[3410][12900]; int path[3410][12900]; int w[3410]; int d[3410]; int totalN; int totalW; int main() { int i,j; scanf("%d",&totalN); scanf("%d",&totalW); for(i=1;i<=totalN;i++) { scanf("%d",&w[i]); scanf("%d",&d[i]); } memset(dp,0,sizeof(dp)); for(i=1;i<=totalN;i++){ for(j=1;j<=totalW;j++){ /*必不可少,需要根据上一轮状态, 而如果直接从j=w[i]开始,会使0<j<w[i]都为0,影响正确的结果。 在之后优化的1维矩阵方法中,就不会存在这种问题, 由于其逆序遍历,上一轮的结果只要不覆盖,就一直存在*/ dp[i][j] = dp[i-1][j]; if(j>=w[i] && dp[i][j]<dp[i-1][j-w[i]]+d[i]){ dp[i][j] =dp[i-1][j-w[i]]+d[i]; path[i][j] = 1; } } } printf("背包内物品的最大价值是: "); printf("%d ",dp[totalN][totalW]); i = totalN; j = totalW; printf("放入背包的物品是: "); while(i>0 && j>0) { if(path[i][j] == 1) { printf("%d ",d[i]); j = j - w[i]; } i = i - 1; } system("pause"); return 0; }
此时的时间复杂度是O(NW),已经无法优化,但空间复杂度可以由O(NW)下降到O(W).。
我们可以看到F(i,j)仅仅依赖于F(i-1,j)和F(i-1,j-w[i])+d[i],也就是说,我们需要依赖的上一轮的序列中的元素的坐标没有在J的右边的,此时我们就可以使用逆序遍历即可,直接应用上一轮的数据。
这样的逆序遍历还有一层意义,即每个物品只放入一次,因为所以来的两项都是没有放入过第i件物品的情况,从不依赖放入第i件物品的情况。这点需要好好的理解一下,有助于后续对完全背包问题的理解。
上代码:
#include <stdio.h> #include <string.h> #include <string> #include <iostream> using namespace std; //数组要设的比给的范围稍大一些 int bag[12900]; int path[3410][12900]; int w[3410]; int d[3410]; int totalN; int totalW; int main() { int i,j; scanf("%d",&totalN); scanf("%d",&totalW); for(i=1;i<=totalN;i++) { scanf("%d",&w[i]); scanf("%d",&d[i]); } memset(bag,0,sizeof(bag)); for(i=1;i<=totalN;i++){ for(j=totalW;j>=w[i];j--){ if(bag[j]<bag[j-w[i]]+d[i]){ bag[j] = bag[j-w[i]]+d[i]; path[i][j]=1; } } } printf("背包内物品的最大价值是: "); printf("%d ",bag[totalW]); i = totalN; j = totalW; printf("放入背包的物品是:"); while(i>0 && j>0) { if(path[i][j] == 1) { printf("%d ",d[i]); j = j - w[i]; } i = i - 1; } system("pause"); return 0; }
最后如果想去AC的话,要采用第二种方案,降低空间复杂度,AC代码(折叠):
#include <stdio.h> #include <string.h> #include <string> #include <iostream> using namespace std; //数组要设的比给的范围稍大一些 int bag[12900]; int w[3410]; int d[3410]; int totalN; int totalW; int main() { int i,j; scanf("%d",&totalN); scanf("%d",&totalW); for(i=1;i<=totalN;i++) { scanf("%d",&w[i]); scanf("%d",&d[i]); } memset(bag,0,sizeof(bag)); for(i=1;i<=totalN;i++){ for(j=totalW;j>=w[i];j--){ if(bag[j]<bag[j-w[i]]+d[i]){ bag[j] = bag[j-w[i]]+d[i]; } } } printf("%d ",bag[totalW]); system("pause"); return 0; }