洛谷 P1048 [NOIP 2005 普及组] 采药 题解
题目大意
给定数组 \(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}}\) 最大。求这个最大值。
思路分析
0-1 背包模板题。
Task 1
定义 \(dp_{i,j}\) 表示前 \(i\) 个元素,\(v\) 总和不超过 \(j\) 的最大 \(w\) 之和。则对于第 \(i\) 个元素,可以选择不取,那么 \(w\) 之和为 \(dp_{i-1,j}\);若选择取,则剩下的 \(i-1\) 个元素的 \(v\) 总和为 \(j-v_i\),所以此时 \(w\) 之和为 \(dp_{i-1,j-v_i}+w_i\)。状态转移方程为:
\[dp_{i,j}=\max(dp_{i-1,j},dp_{i-1,j-v_i}+w_i)
\]
滚动数组
注意到 \(dp_{i,j}\) 只与 \(dp_{i-1,[0..j-1]}\) 有关,所以可以只开一维数组,压掉 \(i\) 这一维,节省空间。但是由于前文所讲,状态转移要用到下标更小的元素,所以 \(j\) 应倒序遍历。
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int N=105,T=1010;
int t,m;
int v[N],w[N],dp[T];
int main(){
scanf("%d%d",&t,&m);
for (int i=1;i<=m;++i) scanf("%d%d",v+i,w+i);
for (int i=1;i<=m;++i){
for (int j=t;j>=v[i];--j) dp[j]=max(dp[j],dp[j-v[i]]+w[i]); // 倒序遍历
}
printf("%d",dp[t]);
return 0;
}

浙公网安备 33010602011771号