题目描述
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≤wij(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;
}
总结
noip 临近,在此提醒大家做题一定要看数据范围,防止因为没开 long long 而遗憾失分。
建议使用 scanf 和 printf,虽然要打的字符多一些,但是时间更快(或者您使用 ios::sync_with_stdio(0)
也可以),防止考场上因为输入输出而 TLE。