详解信奥一本通1290:采药

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. 抽象的(背包)

  1. 直观的(给你个数轴从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;	
}

动态规划四要素

  1. 状态

状态怎么去找?

题目/搜索

三个状态数据使用二维数组

$dp[now][sumw] = sumv$

把题目中随着操作会发生变化的数据全部列出来

A. 时间 B. 草药 C.价值

$dp[now][sumw] = sumv$

// dp[now][sumw] 表示前 now 个草药,消耗时间为 $sumw$ 的最大价值

列出状态数组后,请记得写一句注释

  1. 状态转移方程

考虑当前能做什么事情,每个操作分别会从哪个状态变化到哪个状态

// 拿第 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];
  1. 初始化

就是思考数组一开始要怎么样

一开始没有拿过任何草药,所以所有的价值都为 0

  1. 答案

  1. 转移顺序

当我们使用 $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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值