题目描述
01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 ii 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000输入样例
4 5 1 2 2 4 3 4 4 5
输出样例:
8
代码
#include <iostream>
void func(int **dynamic, int *V, int *W, int v, int w) //函数这里是实现该问题的关键
{
for (int i1 = 0; i1 <= v; i1++)
{
for (int i2 = 0; i2 <= w; i2++)
{
if (i1 == 0 || i2 == 0)
{
dynamic[i1][i2] = 0;
continue;
}
if (W[i1 - 1] > i2)
{
dynamic[i1][i2] = dynamic[i1 - 1][i2];
}
else
{
dynamic[i1][i2] = ((dynamic[i1 - 1][i2]) > (dynamic[i1 - 1][i2 - W[i1 - 1]] + V[i1 - 1]) ? dynamic[i1 - 1][i2] : (dynamic[i1 - 1][i2 - W[i1 - 1]] + V[i1 - 1]));
}
}
}
return;
}
int main()
{
int v = 0, w = 0;
std::cin >> v >> w; //v是物品总数,w是容量,或者说最大承重
int *V = new int[v];
int *W = new int[v];
for (int i = 0; i < v; i++)
std::cin >> W[i] >> V[i]; //V[]是物品价值数组,W[]是物品体积(或者说重量)数组
int **dynamic = new int *[v + 1]; //这段是尝试使用自定义二维数组
for (int i = 0; i <= v; i++)
{
dynamic[i] = new int[w + 1];
}
func(dynamic, V, W, v, w);
std::cout << dynamic[v][w];
return 0;
}
解题思路
-
构建一个从0个物品到N个物品,从0容量到V容量的二维数组(如上图),其中每个下标为[ni][vi]的位置,表示的是在前ni个物品,容量为vi的情况时,所取得的最优解(即所能容纳的最大价值)
-
首先,第零行和第0列,表示的时前0个物品或者容量为0时的最大价值,全为0
-
然后,填充后面的位置时,考虑当前的vi所能是否能容纳第ni个物品,如果能容纳,那么有两个选择,放和不放
-
选择不放时,该处的值为二维数组中上一行的同位置所列出的数,即,当前位置[ni][vi]的值等于[ni-1][vi]的值(因为容积vi相同的情况下,如果不放这第ni件物品,那么当前位置的值仍是前ni件物品时的最优解)
-
选择放,那么放了之后,容积还剩vi-V[ni],那么,当前位置的最优解为第ni个物品的价值value[ni]再加上[ni-1][vi-V[ni]]的值
-
选: f[ni][vi] = value[ni] + f[ni-1][vi-V[ni]]
不选:f[ni][vi] = f[ni-1][vi]两者取最大值,即f[ni][vi]处的最优解
-
-
综上,可得解,列出对应的循环来填充该二维数组,最后取[vi][ni]处的值,即为该问题的解
扩展1:完全背包问题
题目条件
同上,额外增加一个条件:每种物品数量不限
两个代码其实只有一句不同(注意下标)
dynamic[i1 - 1][i2 - W[i1 - 1]] + V[i1 - 1]) //01背包问题
dynamic[i1][i2 - W[i1 - 1]] + V[i1 - 1]) //完全背包问题
核心部分类比上面的序号3里面的内容,就基本上理解了,原理还是同容积下第[ni-1]个物品的最优解已经求出来,
不同的是,在该问题下还要注意的一点是:同一行中,当前关注位置之前的最优解也已求出,所以只用判断当前能不能继续添加,关于加完后的[ni][vi-V[ni]]位置的最优解已经求出,直接代入即可
扩展2:多重背包问题(没解出来)
题目条件
类似于完全背包问题,但是每种物品最多有si件
给我做破防了
找到的题解的思路是,把每件物品的数量拆开,拆成多个只有一个的物品,然后按照01背包问题来做
我的思路是:再加一个一维数组N[]来表示一个数量队列,然后每次使用了一次物品之后数量减一,之后就是=执行01背包问题的代码,但是实现过程中遇到各种各样的问题,勉强能找出问题所在,但是想一个解决办法不行一个,一直到破防
以下是错误的核心部分代码,感兴趣的大佬可以帮忙看看(应该不会有大佬愿意看这么无聊的玩意吧),
for (int i1 = 0; i1 <= v; i1++)
{
int i=N[i1-1];
N[i1-1]*=W[i1-1];
for (int i2 = 0; i2 <= w; i2++)
{
if (i1 == 0 || i2 == 0)
{
dynamic[i1][i2] = 0;
continue;
}
if(N[i1-1]==0){dynamic[i1][i2] = ((dynamic[i1][(W[i1-1]*i+1)>101?101:((W[i1-1]*i)+1)]) > (dynamic[i1][i2-W[i1-1]]+V[i1-1]) ? (dynamic[i1][(W[i1-1]*i+1)>101?101:((W[i1-1]*i)+1)]):(dynamic[i1][i2-W[i1-1]]+V[i1-1]));continue;}
if (W[i1 - 1] > i2) dynamic[i1][i2] = dynamic[i1 - 1][i2];
else
{
if(N[i1-1]!=0) dynamic[i1][i2] = ((dynamic[i1-1][i2]) > (dynamic[i1][i2-W[i1-1]]+V[i1-1]) ? dynamic[i1-1][i2] : ((N[i1-1]--),(dynamic[i1][i2-W[i1-1]]+V[i1-1])));
else {dynamic[i1][i2] = ((dynamic[i1-1][i2]) > (dynamic[i1-1][i2-W[i1-1]]+V[i1-1]) ? dynamic[i1-1][i2] : (dynamic[i1-1][i2-W[i1-1]]+V[i1-1]));}
}
}
}
其他无关的(比扩展2有用一点)
关于动态数组,本质上就是指针指来指去,搞清楚指向位置是变量还是变量的指针,就基本上没问题了
然后,我敲代码的过程中,还遇到一个问题,就是关于普通的二维数组甚至高维数组作为参数传递给函数是,是需要指明除第一维之外的其他维度的具体值,即:
int a[5][10]={0}
则定义函数时,需要指明第二维有多长,即:
void func(int a[][10]);
关于该问题,与c++中的二维数组的形成方式有关,大概是,二维数组存储时仍是以一维数组的形式存储,数组名只是提供一个指向该数组内存空间的指针,如果不说明第二维有多长,那么在函数中调用该数组时,程序就无法理解比如a[2][3]要找的是哪个位置的值
其他无关的里面是我半瓶水晃荡的结果,如有错误,还请大佬们批评指正
以上