01 背包

题目描述

辰辰是个很有潜能、天资聪颖的孩子,他的梦想是称为世界上最伟大的医师。

为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。

医师把他带到个到处都是草药的山洞里对他说:

“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。

我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入描述

输入的第一行有两个整数𝑇(1≤𝑇≤1000)和𝑀(1≤𝑀≤100),𝑇代表总共能够用来采药的时间,𝑀代表山洞里的草药的数目。

接下来的M行每行包括两个在1到100之间(包括1和100)的的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出描述

可能有多组测试数据,对于每组数据:

输出只包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

输入样例1

42 6
1 35
25 70
59 79
65 63
46 6
28 82
962 6
43 96
37 28
5 92
54 3
83 93
17 22
0 0

输出样例1

117
334

知识点标签:背包,搜索,记忆化,剪枝

题解

这应该是实际上手的第一道 01DP 吧,算是人生第一道 dp 了,对一个算法苦守来说还是很困难的,其实很容易看出来,这就是一道十分简单的 dp,如何划分最优子问题,如何定义最优子问题,这些都是很重要的。

对一个通常的 01 背包问题,一定会出现选或者不选的情况,如果是这样就会出现一颗二叉选择树,每一个节点都在选择或者不选择,这会导致复杂度以指数级别升高,自然十分不明智。

对于一个重叠子问题(最优子问题),这道题而言,我们需要最终求得一个 m 个物品的情况下,价值为 n 的结果,如果定义 dp 数组恰好能够模拟这两个参数的情况自然是最好的,那如果对于一个 u 个输入的数组而言,** \(t_i\) 是采集草药所花费的时间,而 \(v_i\) 是第 i 个草药的价值 **,dp 数组的大小当然最好是大于 u 个输入的,因为总会存在一个可能性会把所有的草药都采摘,所以 dp 数组的两个参数显而易见和输入有关,那到底该如何定义 dp 数组的含义呢?

我想,如果需要同时满足我们上述的限制条件和最优子问题,那至少要求一个 dp[\(i\)][\(j\)] 在 \(i_{1}> i_2\) 并且 \(j_{1}> j_2\) 的情况下前者应当包括后者的结果,那么 ij 至少是和选择有关的,这么说起来似乎可以定义成,前 i 个物品在 j 的时间内获得的最大价值,那岂不是美哉?

在这个情况下似乎是可以实现某种可能性的转移的。
image.png

那么如何确定转移又成了现在的难题,对当前的 dp[i][j]来说,i 是前 i 个物品,但并不能溯源前 i-1 个物品,所以也就是这第 i 个物品选或者不选,就成了从 i-1 个物品转移到第 i 个物品的解决方案。

这个转移是很好想的,对第 i 个物品,要么选了,要么没选,

  • 如果没选,那么 dp[i][j]=dp[i-1][j]=dp[i-1][j-0]+0 (j 不需要减少,背包的空没有减少)
  • 如果选了,那么 dp[i][j]=dp[i-1][j- \(t_i\)] + \(v_i\) ,也就是说原先的状态是 dp[i-1][j- \(t_i\)] 因为啊因为啊到 i 的时候时间是 j,那么 i-1 的时间其实是从 i 这里回退的才对,只是这种回退可以写成具体的表达式,又变成了正向的前进,后面加上的 \(v_i\) 自然是很好理解,加上了选择 i 的价值而已。
    image.png

转移的事情考虑的差不多了,应当考虑如果写具体的实现代码应当如何初始化了,那自然 dp[0][0~j]都是应当初始化为 0 的。

M 是总数量,T 是所有事件的总时间。

最终要的答案是 dp[M][T],求解的过程中需要用到前面的子问题,所以应当是从 dp[i][j] 中开始,i=0 的时候用作初始化,但是 j=0 的时候是需要进行遍历的,我们并不好排除有些情况下不消耗时间也能得到一定的价值,所以就这样,本题完结。

如果这样的话可以写出一些代码了

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll T, M;
ll dp[110][1010];
ll t[110], v[110];

void solve() {
  for (int i = 0; i <= T; ++i) {
    dp[0][i] = 0;
  }
  for (int i = 1; i <= M; ++i) {
    cin >> t[i] >> v[i];
  }
  for (int i = 1; i <= M; ++i) {
    for (int j = 0; j <= T; ++j) {
    	
      // 加上其他判断,首先得时间够
      // 也就是说如果要选第i个物品,那前提是保证i-1的时候有 j-t[i]
      // 是正数,这样可以确定时间是足够的才会选择第i个物品
      if (j >= t[i])
        dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - t[i]] + v[i]);
      else
        dp[i][j] = dp[i - 1][j];
    }
  }
  cout << dp[M][T] << '\n';
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  while (cin >> T >> M) {
    if (T == 0 && M == 0) break;
    solve();
  }
  return 0;
}
posted @ 2024-04-22 00:02  Phantasia1116  阅读(20)  评论(0)    收藏  举报