一、Bone Collector

Problem Description
许多年前,在泰迪的家乡,有一个人被称为“骨收集者”。骨头收集者有一个大袋子,里面装满了V,在收集骨头的过程中,不同的骨骼具有不同的值和不同的体积,现在给定每个骨骼的值,您能否计算出骨骼收集器可获得的总值的最大值?

Input
第一行包含整数T,即案例数。
紧随其后的是T个案例,每个案例三行,第一行包含两个整数N,C(N <= 1000,C <= 1000),
表示骨头的数量和包的体积。第二行包含代表每个骨骼值的N个整数。第三行包含N个整数,代表每个骨骼的体积。

Output
每行一个整数,代表总和的最大值(该数字将小于2^31^)。

Sample Input

1
5 10
1 2 3 4 5
5 4 3 2 1

Sample Output

14

解法一:二维数组解法(0/1背包模板代码)

tip:注意多组数据要清dp数组为零,忘记了所以wa了一次。
图片说明
一道模板题~ 根据上次讲解的0/1背包推导公式(0/1背包讲解戳这里~.),并用条件语句实现公式即可。
二维表是N+1行(第0行是前0个物品的情况,也就是没有东西可以装),
C+1(第0列是背包0容量的情况)。
第0行和第0列的dp数组值都是0,其他根据公式填入,最大(最优)的情况就是二维数组的最右下角那个格子的值即dp[N][C]。

#include<bits/stdc++.h>
#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;
  }
}

1.1 0/1背包打印方案代码

判断所填的值dp[i][j]是等于dp[i-1][j-ni]+vi还是dp[i-1][j]
用条件语句直接判别两种情况:

  1. 若等于dp[i-1][j-ni]+vi,则a[i]=1,表明选了第i个物品,j更新为j-ni

  2. 若等于dp[i-1][j],a[i]=0(不用赋值,已初始化),表明没选第i个物品,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]即是最优解。
在这里插入图片描述