【NOIP2005 普及组】采药 详解 —— 0-1 背包入门

一道 0-1 背包模版题。

首先对于每株草药,只有两种情况:不取。那么我们就可以设一个数组(出于考虑有同学没学过动态规划,所以暂时不叫他 DP 状态), f i , j = k f_{i,j}=k fi,j=k 表示只采前 i i i 株草药,时间为 j j j 时,最多能采到总价值为 k k k 的草药。

这样我们就可以开始考虑如何得到 f i , j f_{i,j} fi,j。假如我们已经处理好了前 i − 1 i-1 i1 株采药的所有情况,那么对于第 i i i 株草药,我们就可以分两种情况:

  1. 如果不取,那么就很简单, f i , j f_{i,j} fi,j 还是等于 f i − 1 , j f_{i-1,j} fi1,j
  2. 如果,这时候剩余的时间就会减少采这株采药所需的时间( t i t_i ti,但总价值就会加上这株草药的价值( v i v_i vi,所以这时候 f i , j f_{i,j} fi,j 就会等于 f i − 1 , j − t i + v i f_{i-1,j-t_i}+v_i fi1,jti+vi

考虑完两种情况后,我们只需要找他们两个中更优的方案,让 f i , j = max ⁡ ( f i − 1 , j , f i − 1 , j − t i + v i ) f_{i,j}=\max(f_{i-1,j},f_{i-1,j-t_i}+v_i) fi,j=max(fi1,j,fi1,jti+vi) 就可以了,我们管这个公式叫转移方程

这样我们就可以得到代码了:

核心代码

for(int i = 1; i <= M; i++)
	for(int j = 0; j <= T; j++)
		f[i][j] = max(f[i-1][j], f[i-1][j-t[i]]+v[i]);

但是如果你这么交上去的话就会发现全都 WA \texttt{WA} WA 了,这是为什么呢?

其实我们仔细观察第 2 , 3 2,3 2,3 行,我们的 j j j 1 1 1 开始枚举,但是转移方程中有这样一个东西:j-t[i]。这样是不是就发现问题了?如果 j j j 要是小于 t i t_i ti 怎么办?所以我们的 j j j 要按 T → 0 T \to 0 T0 枚举,并在代码中加一个判断,这样就可以完美解决这个问题:

完整代码

#include <iostream>
using namespace std;

const int N = 1e3+5;
int t[N], v[N];
int f[105][N];

int main()
{
	int T, M;
	cin >> T >> M;
	for(int i = 1; i <= M; i++)
		cin >> t[i] >> v[i];
	for(int i = 1; i <= M; i++)
		for(int j = T; j >= 0; j--)
		{
			if(j >= t[i])
				f[i][j] = max(f[i-1][j], f[i-1][j-t[i]]+v[i]);
			else
				f[i][j] = f[i-1][j];
		}
	cout << f[M][T] << endl;
	return 0;
}

于是,我们就顺利通过此题了!

但是我们其实还可以将 f f f 数组优化成一维的,接下来就来看一看如何优化。

如果大家有空间这个概念的话,就应该思考一个问题:如果题目中 M , T M,T M,T 的更大怎么办?如果开二维数组就有可能出现 MLE \texttt{MLE} MLE,这时候我们就需要对代码进行优化。

首先来看,当我们处理到第 i i i 株草药时,不难发现,对 f i f_i fi 有影响的只有 f i − 1 f_{i-1} fi1,所以可以直接去掉第一维,直接用 f j = k f_j=k fj=k 表示当总时间为 j j j 时,最多可获得总价值为 k k k 的草药。于是就可以得出转移方程: f j = max ⁡ ( f j , f j − w i + v i ) f_j=\max(f_j,f_{j-w_i}+v_i) fj=max(fj,fjwi+vi),这样代码是不是就十分简单了呢?但可能有人说,这样会重复放入。

说的没错,仔细观察代码可以发现:当 j ≥ t i j\geq t_i jti 时, f i , j f_{i,j} fi,j 会被 f i , j − t i f_{i,j-t_i} fi,jti 影响,因此第 i i i 株草药会被多次采摘。如果想解决这个问题其实也很简单,我们只需改变枚举顺序,按 T → t i T \to t_i Tti 枚举,这时 f i , j f_{i,j} fi,j 就会在 f i , j − t i f_{i,j-t_i} fi,jti 前更新了。所以代码就是如下所示:

AC Code

#include <iostream>
using namespace std;

const int N = 1e3+5;
int t[N], v[N];
int f[N];

int main()
{
    int T, M;
    cin >> T >> M;
    for(int i = 1; i <= M; i++)
        cin >> t[i] >> v[i];
    for(int i = 1; i <= M; i++)
        for(int j = T; j >= t[i]; j--)
            f[j] = max(f[j], f[j-t[i]]+v[i]);
    cout << f[T] << endl;
    return 0;
}

是不是非常简短?

后记

其实背包问题不光只有 0-1 背包,还有完全背包、多重背包等变形问题。如果你还想学习这些问题,可以参考这里

最后,希望这篇题解可以帮到你,祝你在 OI 的路上越走越远,慢慢喜欢上编程,获得更多成就!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值