洛谷 P1616 疯狂的采药 题解
题目大意
给定数组 \(v_{[1..n]}\) 和 \(w_{[1..n]}\),从中选取任意个下标 \(p_{[1..m]}\) 使 \(\sum_{i=1}^m{v_{p_i}}\le t\) 且 \(\sum_{i=1}^m{w_{p_i}}\) 最大,其中下标可重复。求这个最大值。
思路分析
完全背包模板题。
Task 1
定义 \(dp_{i,j}\) 表示前 \(i\) 个元素,\(v\) 总和不超过 \(j\) 的最大 \(w\) 之和。则对于第 \(i\) 个元素,可以选择不取,那么 \(w\) 之和为 \(dp_{i-1,j}\);若选择取,由于不知道取几个,则可以认为是在 \(dp_{i,j-v_i}\) 的基础上再选,所以此时 \(w\) 之和为 \(dp_{i,j-v_i}+w_i\)。状态转移方程为:
\[dp_{i,j}=\max(dp_{i-1,j},dp_{i,j-v_i}+w_i)
\]
滚动数组
注意到 \(dp_{i,j}\) 只与 \(dp_{i-1,j}\) 与 \(dp_{i,[0..j-1]}\) 有关,所以可以只开一维数组,压掉 \(i\) 这一维,节省空间。但是由于前文所讲,状态转移要用到更改完之后下标更小的元素,所以 \(j\) 仍应正序遍历。这是其与 0-1 背包不同的地方。
代码呈现
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=1e4+10,T=1e7+10;
int t,m;
int a[M],b[M];
ll dp[T];
int main(){
scanf("%d%d",&t,&m);
for (int i=1;i<=m;++i) scanf("%d%d",a+i,b+i);
for (int i=1;i<=m;++i){
for (int j=a[i];j<=t;++j) dp[j]=max(dp[j],dp[j-a[i]]+b[i]);
}
printf("%lld",dp[t]);
return 0;
}

浙公网安备 33010602011771号