1290:采药
【题目描述】
辰辰是个很有潜能、天资聪颖的孩子,他的梦想是称为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
【输入】
输入的第一行有两个整数T(1<=T<=1000)和M(1<=M<=100),T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的的整数,分别表示采摘某株草药的时间和这株草药的价值。
【输出】
输出只包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
【输入样例】
70 3
71 100
69 1
1 2
【输出样例】
3
思路
基础的动态规划
线性动态规划
抽象的(背包)
直观的(给你个数轴从1到n,给你个矩阵从1,1到n,n)
01背包
给出 n 个草药,第 i 个草药需要花费 $w_i$ 的时间,这个草药的价格为 $v_i$
时间最多为 $T$,问最多能拿到多少价值的草药
int dp[110][1100];
// 表示当前判断第 now 个草药,前面已选的草药总时间为 sum_v, 已选草药总价值为 sum_v
void dfs(int now, int sum_w, int sum_v){
// 2.出口
if (now > n){
ans = max(ans, sum_v);
return;
}
// 之前到达 now sum_w 的草药最大价值如果大于等于现在的 sum_v
if (dp[now][sum_w] >= sum_v){
// 那就没有必要继续搜索了,因为之前搜过的更好
return;
}
// 否则当前这一次搜索更好,那就记录最大值
dp[now][sum_w] = sum_v;
// 1.能做的事情
if (sum_w + w[now] <= T){
dfs(now + 1, sum_w + w[now], sum_v + v[now]);
}
dfs(now + 1, sum_w, sum_v);
}
int main(){
...
memset(dp, -1, sizeof(dp));
dfs(1, 0, 0);
return 0;
}
动态规划四要素
状态
状态怎么去找?
题目/搜索
三个状态数据使用二维数组
$dp[now][sumw] = sumv$
把题目中随着操作会发生变化的数据全部列出来
A. 时间 B. 草药 C.价值
$dp[now][sumw] = sumv$
// dp[now][sumw] 表示前 now 个草药,消耗时间为 $sumw$ 的最大价值
列出状态数组后,请记得写一句注释
状态转移方程
考虑当前能做什么事情,每个操作分别会从哪个状态变化到哪个状态
// 拿第 now 个草药之后会变成什么样
if (sum_w + w[now] <= T){
当前状态是 dp[now][sumw]
拿了之后会变化到 dp[now + 1][sum_w + w[now]
操作的效果是 + v[now]
得到方程 dp[now + 1][sum_w + w[now]] = dp[now][sumw] + v[now];
}
// 不拿第 now 个草药之后会变成什么样
当前状态是 dp[now][sumw]
不拿会变化到 dp[now + 1][sum_w]
操作的效果是 没有效果
得到方程 dp[now + 1][sum_w] = dp[now][sumw]
状态转移方程最常见的两种写法(转移方向):1. 到哪去 2. 从哪来
就是思考 $dp[now][sumw]$ 能从哪些状态转移过来
// 拿第 now 个草药之后变成了 dp[now][sumw]
拿之前是 dp[now-1][sumw - w[now]]
拿完之后变成了 dp[now][sumw]
操作的效果 + v[now]
得到方程 dp[now][sumw] = dp[now - 1][sumw - w[now]] + v[now];
// 不拿第 now 个草药之后变成了 dp[now][sumw]
不拿之前是 dp[now-1][sumw]
拿完之后变成了 dp[now][sumw]
操作的效果 没有效果
得到方程 dp[now][sumw] = dp[now - 1][sumw];
初始化
就是思考数组一开始要怎么样
一开始没有拿过任何草药,所以所有的价值都为 0
答案
转移顺序
当我们使用 $dp[now][sumw]$ 时,要保证 $dp[now][sumw]$ 已经是最优解
状态的定义:dp[now][sumw] 表示前 now 个草药花费时间正好是 sumw 的最大价值
状态转移方程:dp[now][sumw] = max(dp[now - 1][sumw], dp[now - 1][sumw - w[now]]);要保证状态存在才能转移
初始化:dp[0][0] = 0 一开始没有草药,没有花时间,价值为 0
答案: dp[n][t]
#include <bits/stdc++.h>
using namespace std;
int w[10000], v[10000];
int dp[110][1100];
int main() {
int t, n;
cin >> t >> n;
for (int i = 1; i <= n; ++i){
cin >> w[i] >> v[i];
}
memset(dp, -1, sizeof(dp));
dp[0][0] = 0;
for (int now = 1; now <= n; ++now){
for (int sumw = 0; sumw <= t; ++ sumw){
if (dp[now - 1][sumw] != -1){
dp[now][sumw] = dp[now - 1][sumw];
}
if (sumw - w[now] >= 0 && dp[now - 1][sumw - w[now]] != -1){
dp[now][sumw] = max(dp[now][sumw], dp[now - 1][sumw - w[now]] + v[now]);
}
}
}
int ans = 0;
for (int i = 0; i <= t; ++i){
ans = max(ans, dp[n][i]);
}
cout << dp[n][t] << endl;
return 0;
}
状态的定义:dp[now][sumw] 表示前 now 个草药花费时间不超过 sumw 的最大价值
状态转移方程:dp[now][sumw] = max(dp[now - 1][sumw], dp[now - 1][sumw - w[now]]);
初始化:全部为0,一开始假设所有时间都可以被浪费掉,花费任意的时间均没有收入
答案: dp[n][t]
#include <bits/stdc++.h>
using namespace std;
int w[10000], v[10000];
int dp[110][1100];
int main() {
int t, n;
cin >> t >> n;
for (int i = 1; i <= n; ++i){
cin >> w[i] >> v[i];
}
for (int now = 1; now <= n; ++now){
for (int sumw = 0; sumw <= t; ++ sumw){
dp[now][sumw] = dp[now - 1][sumw];
if (sumw - w[now] >= 0){
dp[now][sumw] = max(dp[now][sumw], dp[now - 1][sumw - w[now]] + v[now]);
}
}
}
cout << dp[n][t] << endl;
return 0;
}