洛谷 P1616 疯狂的采药 题解

题目大意

洛谷 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;
}
posted @ 2025-12-14 20:50  CodingJuRuo  阅读(6)  评论(0)    收藏  举报