HDOJ 2602-Bone Collector(0/1背包模板、打印方案及滚动数组解法)

一、Bone Collector

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

解法一:二维数组解法(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]即是最优解。
在这里插入图片描述

2.1 一维滚动数组例题

E-爱玩游戏的Tom

题目链接

题目描述
Tom很喜欢玩游戏,他在电脑上下了《GTA5》和《微软飞行模拟》,对于后者他还丧心病狂的下载了所有地图包,导致他可用空间只有mGM,但他还有几个学校要求安装的软件没有下载,他不能全部放下去,因此只能选择性的安装一部分。现在,我们知道每个学习软件的大小以及该学习软件的重要程度,现在Tom找到你,应该安装哪一些软件,使得这些软件的重要程度之和最大。
对于输入第一行有n(0 < n < 100)和m(0 < m < 10000)两个整数组成,n代表接下来的输入行数,m代表可用空间还剩mGB。
接下来的n行,每行两个整数,前一个整数为每个学习软件的大小(GB),后一个整数代表重要程度。
对于输出,只有一行,即为最大重要程度之和。

输入描述:
第一行输入n和m,n代表接下来的行数,m代表可用空间剩余内存,整数之间空格隔开
接下来的n行,由两个整数组成,前一个为每个软件大小,后一个整数代表重要程度,整数之间空格隔开

输出描述:
输出只有一行且只有一个整数,即为最大重要程度之和

示例1
输入

3 23
15 9
20 5
7 7

输出

16

说明
1、输入:
第一行3表示接下来有3行,23表示还剩23GB
第一行后的三行前一个整数表示学习软件大小(GB),后一个整数表示重要程度
2、输出:
16,表示最大重要程度之和,安装的软件为15GB的和7GB的

AC代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
ll dp[maxn];
ll t, n, v;
struct node {
  int vi, wi;
} w[maxn];

ll ans() {
  memset(dp, 0, sizeof(dp));
  for (int i = 1; i <= n; ++i)
    for (int j = v; j >= w[i].wi; --j)
      dp[j] = max(dp[j], dp[j - w[i].wi] + w[i].vi);
  return dp[v];
}
int main() {
  scanf("%lld%lld", &n, &v);
  for (int i = 1; i <= n; ++i)
      scanf("%d%d", &w[i].wi, &w[i].vi);
  printf("%lld\n", ans());
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你脸上有BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值