疯狂的采药

题目描述

LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

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

此题和原题的不同点:

11. 每种草药可以无限制地疯狂采摘。

22. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入格式

输入第一行有两个整数,分别代表总共能够用来采药的时间 tt 和代表山洞里的草药的数目 mm。

第 22 到第 (m + 1)(m+1) 行,每行两个整数,第 (i + 1)(i+1) 行的整数 a_i, b_iai​,bi​ 分别表示采摘第 ii 种草药的时间和该草药的价值。

输出格式

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

输入输出样例

输入 #1复制

70 3
71 100
69 1
1 2

输出 #1复制

140

说明/提示

数据规模与约定

  • 对于 30\%30% 的数据,保证 m \le 10^3m≤103 。
  • 对于 100\%100% 的数据,保证 1 \leq m \le 10^41≤m≤104,1 \leq t \leq 10^71≤t≤107,且 1 \leq m \times t \leq 10^71≤m×t≤107,1 \leq a_i, b_i \leq 10^41≤ai​,bi​≤104。

一道考察完全背包的题目。

题目传送门

upd:添加了对完全背包状态转移方程式的详细推导过程。


正文

设 f_{i,j}fi,j​ 表示前 ii 件物品采药 jj 时间能获得的最大价值。

可以列出状态转移方程:

f_{i,j}=\max\limits_{k=0}^{k\le \frac{j}{w_i}} (f_{i-1,j-k\times w_i}+k\times v_i)fi,j​=k=0maxk≤wi​j​​(fi−1,j−k×wi​​+k×vi​)

使用二维 dp 显然超时超空间,考虑优化 dp。

把第 ii 种草药拆成体积为 w_i\times 2wi​×2 价值 v_i\times 2vi​×2 的物品,其中满足 w_i\times 2\le jwi​×2≤j。

  • 对于第 ii 种草药的出现,我们对第 ii 种草药要不要采进行决策。如果不放那么 f_{i,j}=f_{i-1,j}fi,j​=fi−1,j​;

  • 如果确定采,那么应该出现至少一个第 ii 种草药,所以当前至少应该出现一个第 ii 种草药,即 f_{i,j}=f_{i,j-w_i}+v_ifi,j​=fi,j−wi​​+vi​。

  • f_{i,j-w_i}fi,j−wi​​ 里面可能有第 ii 种草药,也可能没有第 ii 种草药。我们要确保当前至少有一个第 ii 个草药,所以要预留 w_iwi​ 的空间来存放一个第 ii 种草药。

那么完全背包和 01 背包的不同点在哪里呢?

  • 从二维数组上区别 01 背包和完全背包也就是状态转移方程就差别在采第 ii 种草药时,完全背包在选择采这个草药时,最优解是 f_{i,j-w_i}+v_ifi,j−wi​​+vi​ 即同行的那一个,而 01 背包比较的是f_{i-1,j-w_i}+v_ifi−1,j−wi​​+vi​,上一行的那一个。

时间优化完成了,但是此时空间还是会超,考虑滚动数组。

是否能滚动数组呢?答案当然是可以的。

假设只有一惟 f_ifi​,如果是倒序推那么当前 f_1f1​ ~ f_{i-1}fi−1​ 都是上一个草药遗留下来的状态,显然不符合要求。

正序推的话 f_1f1​ ~ f_{i-1}fi−1​ 均为当前草药已经推过的状态,符合要求。

所以完全背包和 01 背包的区别就在于对时间大小枚举的顺序不同。

具体代码也就好写了:

for(int i=1;i<=n;i++)
	for(int j=w[i];j<=m;j++)
		f[j]=max(f[j],f[j-w[i]]+v[i]);

以上为核心代码。

接下来要注意本题数据范围(有一个坑点)

本题最多有 10^7107 时间,每种草药的价值最大是 10^4104,所以极限情况下价值总和是 10^7\times 10^4=10^{11}107×104=1011,会爆 int,所以要开 long long。

十年 OI 一场空,不开 long long 见祖宗。

Code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+5,M=1e7+5;
int n,m,w[N],v[N],f[M];
signed main(){
	scanf("%lld%lld",&m,&n);
	for(int i=1;i<=n;i++)
		scanf("%lld%lld",&w[i],&v[i]);
	for(int i=1;i<=n;i++)
		for(int j=w[i];j<=m;j++)
			f[j]=max(f[j],f[j-w[i]]+v[i]);
	printf("%lld",f[m]);
	return 0;
}

AC!


总结

noip 临近,在此提醒大家做题一定要看数据范围,防止因为没开 long long 而遗憾失分。

建议使用 scanf 和 printf,虽然要打的字符多一些,但是时间更快(或者您使用 ios::sync_with_stdio(0) 也可以),防止考场上因为输入输出而 TLE。

\sf{The\,End.}TheEnd.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值