Problem Description
许多年前,在泰迪的家乡,有一个人被称为“骨收集者”。骨头收集者有一个大袋子,里面装满了V,在收集骨头的过程中,不同的骨骼具有不同的值和不同的体积,现在给定每个骨骼的值,您能否计算出骨骼收集器可获得的总值的最大值?
Input
第一行包含整数T,即案例数。
紧随其后的是T个案例,每个案例三行,第一行包含两个整数N,C(N <= 1000,C <= 1000),
表示骨头的数量和包的体积。第二行包含代表每个骨骼值的N个整数。第三行包含N个整数,代表每个骨骼的体积。
Output
每行一个整数,代表总和的最大值(该数字将小于231)。
Sample Input
1
5 10
1 2 3 4 5
5 4 3 2 1
Sample Output
14
tip:注意多组数据要清dp数组为零,忘记了所以wa了一次。
一道模板题~根据上次讲解的0/1背包推导公式(0/1背包讲解戳这里~.),并用条件语句实现公式即可。
二维表是N+1行(第0行是前0个物品的情况,也就是没有东西可以装),
C+1(第0列是背包0容量的情况)。
第0行和第0列的dp数组值都是0,其他根据公式填入,最大(最优)的情况就是二维数组的最右下角那个格子的值即dp[N][C]。
#include
#define fio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn = 1e3+1;
int N, C;
int dp[maxn][maxn];
struct {
int v;
int n;
}node[maxn];
void back() {
for (int i = 0; i <= N; i++) dp[i][0] = 0;
for (int i = 1; i <= N; i++) {
for (int j = 0; j <= C; j++) {
if (node[i].n > j) { //第i个物品太大装不下
dp[i][j] = dp[i-1][j];
} else {//第i个物品可以装,选择装或不装,选择两种情况结果最大的。
dp[i][j] = max(dp[i-1][j-node[i].n]+node[i].v, dp[i-1][j]);
}
}
}
}
int main() {
fio
int T;
cin >> T;
while (T--) {
cin >> N >> C;
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= N; i++) cin >> node[i].v;
for (int i = 1; i <= N; i++) cin >> node[i].n;
back();
cout << dp[N][C] << endl;
}
}
判断所填的值dp[i][j]是等于dp[i-1][j-ni]+vi还是dp[i-1][j]
用条件语句直接判别两种情况:
两种情况 i 都要减1。
int a[maxn];//记录解的数组
void output() {
int i = N, j = C;
int r = N;
while (r--) {
if (dp[i][j] == (dp[i-1][j-node[i].n]+node[i].v)) {
j = j-node[i].n;
a[i] = 1;
}
i -= 1;
}
for (int i = 1; i <= N; i++)
if (a[i])
cout << i << " ";
}
适合:N、C非常大的情况
不适合:需要打印具体方案
把二维dp[][]变成一维的dp[],这个解放可以节省更多的空间。二维dp[][]中每一行是由上一行计算出来的,所以只跟上一行有关。
故我们可以直接用新的一行覆盖原来一行。
int dp[1001];
void back() {
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= N; i++)
for (int j = C; j >= node[i].n; j--)
dp[j] = max(dp[j], dp[j-node[i].n]+node[i].v);
cout << dp[C] << endl;
}
初始化为0,第一次dp是从物品1开始,倒着从最大容量往物品的重量的方向依次填入值(也就是图中以5为分界,5前面的位置填原来的值,不更新;5后面的值计算填入),倒着计算可以省掉前面几个不必计算的空格,减少计算量。
重复上述覆盖操作,即可得到第五次dp的结果,取dp[N]即是最优解。