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 的时间内获得的最大价值,那岂不是美哉?
在这个情况下似乎是可以实现某种可能性的转移的。

那么如何确定转移又成了现在的难题,对当前的 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 的价值而已。

转移的事情考虑的差不多了,应当考虑如果写具体的实现代码应当如何初始化了,那自然 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;
}

浙公网安备 33010602011771号