WUSTOJ 1308: 采药(Java)动态规划-01背包

本文详细解析了一道经典的计算机科学问题——草药采集问题。通过01背包问题的算法,探讨了如何在有限时间内,从多种草药中选择价值最大的组合。提供了两种实现方式:动态规划的双重循环和递归算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:?1308: 采药

Description

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

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

Input

输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

Output

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

Sample Input

70 3
71 100
69 1
1 2

Sample Output

3

HINT

对于30%的数据,M <= 10;

对于全部的数据,M <= 100。

分析?

01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。

一共有M株草药,总共能够采摘的时间为T,每株草药的采摘时间为T1T2Tm,与之对应的价值为V1V2Vm

value[i][j]表示在“选择”(选择不代表采摘)完第i株后,使用的总时间为j的情况下的总价值。

  • 如果总时间j比采摘第i株的时间Ti还小的话,显然第i株不能采摘,因此选择完第i株后的总价值value[i][j]和选择完第i-1株后的总价值相同。即value[i][j]=value[i-1][j]
  • 如果总时间j足够采摘第i株草药
    • 不采摘第i株后,总价值value[i][j]和选择完第i-1株后的总价值相同value[i-1][j]
    • 采摘第i株后,总价值value[i][j]等于选择完第i-1株后的总价值i株的价值,即value[i-1][j-Ti]+Vi
    • 故,选择完第i株后的总价值value[i][j] = Math.max(value[i - 1][j], value[i - 1][j - Ti] + Vi)
示例:

输入

5 3
1 2
2 3
4 4

选择完后的value值如下:

012345
0000000
1022222
2023555
3023556

输出

6

代码?双重循环

/**
 * Time 500ms
 * @author wowpH
 * @version 1.2
 * @date 2019年6月14日下午4:14:14
 * Environment:	Windows 10
 * IDE Version:	Eclipse 2019-3
 * JDK Version:	JDK1.8.0_112
 */

import java.io.InputStreamReader;
import java.util.Scanner;

public class Main {
	private Scanner sc = new Scanner(new InputStreamReader(System.in));
	private int T, M;		// 总共能够用来采药的时间,山洞里的草药的数目
	private Herb[] herbs;	// 草药,下标从1开始
	private int[][] value;	// 采摘的草药的价值

	public Main() {
		while (sc.hasNext()) {
			init();
			System.out.println(solve());
		}
		sc.close();
	}

	private void init() {// 初始化
		T = sc.nextInt();
		M = sc.nextInt();
		herbs = new Herb[M + 1];
		herbs[0] = new Herb();
		for (int i = 1; i <= M; ++i) {
			herbs[i] = new Herb();
			herbs[i].time = sc.nextInt();
			herbs[i].value = sc.nextInt();
		}
		value = new int[M + 1][T + 1];
	}

	private int solve() {
		int temp;
		for (int i = 1; i <= M; ++i) {// M株草药
			for (int j = 1; j <= T; ++j) {
				if (j < herbs[i].time) {// 时间不够采摘第i株草药的情况
					value[i][j] = value[i - 1][j];
				} else {
					// 采摘第i株草药后的价值
					temp = value[i - 1][j - herbs[i].time] + herbs[i].value;
					// 取“不采摘”和“采摘”第i株的价值较大的情况
					value[i][j] = Math.max(value[i - 1][j], temp);
				}
			}
		}
		return value[M][T];// 返回最大价值
	}

	public static void main(String[] args) {
		new Main();
	}
}

class Herb {
	int time;	// 采摘草药时间
	int value;	// 草药的价值
}

代码?递归

/**
 * Time 625ms
 * @author wowpH
 * @version 2.0
 * @date 2019年6月15日下午9:52:12
 * Environment:	Windows 10
 * IDE Version:	Eclipse 2019-3
 * JDK Version:	JDK1.8.0_112
 */

import java.io.InputStreamReader;
import java.util.Scanner;

public class Main {
	private int[][] herbs, value;//草药,总价值

	public Main() {
		Scanner sc = new Scanner(new InputStreamReader(System.in));
		int T, M;// 总共能够用来采药的时间,山洞里的草药的数目
		while (sc.hasNext()) {
			T = sc.nextInt();
			M = sc.nextInt();
			herbs = new int[M + 1][2];// 下标从1开始
			for (int i = 1; i <= M; ++i) {
				herbs[i][0] = sc.nextInt();// 时间
				herbs[i][1] = sc.nextInt();// 价值
			}
			value = new int[M + 1][T + 1];
			solve(M, T);
			System.out.println(value[M][T]);
		}
		sc.close();
	}

	private void solve(int m, int t) {
		if (m < 1) {// 递归结束条件
			return;
		}
		if (herbs[m][0] > t) {// 时间不够
			if (0 == value[m - 1][t]) {
				solve(m - 1, t);// 选择前一株
			}
			value[m][t] = value[m - 1][t];// 和前一株价值相同
		} else {// 时间够
			// 不采摘第m株
			if (0 == value[m - 1][t]) {// 前一株对应位置未计算过
				solve(m - 1, t);// 递归选择前一株
			}
			// 采摘第m株
			if (0 == value[m - 1][t - herbs[m][0]]) {// 没计算过
				solve(m - 1, t - herbs[m][0]);// 递归计算
			}
			// 采摘后的值
			int b = value[m - 1][t - herbs[m][0]] + herbs[m][1];
			value[m][t] = Math.max(value[m - 1][t], b);// 取较大值
		}
	}

	public static void main(String[] args) {
		new Main();
	}
}

版权声明:

  1. 转载请于首页注明链接形式的WUSTOJ 1308: 采药(Java)——wowpH
  2. 代码原创,公开引用不能删除首行注释(作者,版本号,时间等信息);
  3. 如果有疑问欢迎评论区留言,尽量解答;
  4. 如果有错误,还望大侠评论区指正。

转载于:https://www.cnblogs.com/wowpH/p/11060759.html

<think>首先,用户的问题是:“我想查找01背包问题的动态规划实现或解析 请问01背包问题 动态规划 实现方法 解析”。这是一个关于01背包问题的动态规划实现解析的查询。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用有四个: - [^1]: 关于动态规划0/1背包问题的深入解析。 - [^2]: 动态规划——0/1背包问题(全网最细+图文解析)- [^3]: 动态规划算法深度解析:0-1背包问题(含完整流程)。 - [^4]: 01背包问题(采药动态规划,python实现。 这些引用提供了01背包问题的背景信息,包括定义、状态转移方程、码实现等。我需要基于这些信息构建回答。 关键点从引用中提取: - 01背包问题:给定n个物品,每个物品有重量w_i价值v_i,背包容量W。目标是选择物品放入背包,使得总重量不超过W,且总价值最大。每个物品要么选(1)要么不选(0)。 - 动态规划方法:使用二维状态表dp[i][j],表示前i个物品在背包容量j时的最大价值。 - 状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i-1][j - w_i] + v_i) if j >= w_i, else dp[i-1][j]。 - 初始化:dp[0][j] = 0 for all j, since no items. - 最终结果:dp[n][W]。 从引用[3]:有具体的参数定义初始化码。 从引用[4]:有Python实现,但用户提到的是采药问题,是01背包的变体。 我的回答结构应该清晰: 1. **介绍01背包问题**:简要解释问题。 2. **动态规划解析**:逐步解释如何建模问题,包括子问题定义、状态定义、状态转移方程、初始条件、计算过程。 3. **实现方法**:提供伪码或实际码(如Python),基于引用。 4. **示例**:给出一个简单示例来演示过程。 5. **总结**:简要回顾。 6. **生成相关问题**:在最后添加相关问题,使用§§相关问题§§格式。 使用中文回答。 确保数学表达式正确: - 行内:如背包容量$W$,价值$v_i$。 - 独立公式:如状态转移方程。 引用标识:在回答中,如果引用特定部分,添加引用标识,如[^3]。 现在,构建回答大纲: 1. **问题定义**:解释01背包问题。 2. **动态规划步骤**: - 定义子问题。 - 状态表示:$dp[i][j]$。 - 状态转移方程:$$dp[i][j] = \begin{cases} \max(dp[i-1][j], dp[i-1][j - w_i] + v_i) & \text{if } j \geq w_i \\ dp[i-1][j] & \text{otherwise} \end{cases}$$ - 初始化:$dp[0][j] = 0$ for $j \geq 0$。 - 计算顺序:从i=1到n,j=0到W。 - 结果:$dp[n][W]$。 3. **码实现**:提供Python码示例,基于引用[3]或[4]。 4. **示例解析**:用一个小例子展示计算过程。 5. **相关问题生成**。 生成相关问题: - 如:1. 如何优化01背包问题的空间复杂度? 2. 01背包问题有哪些变体? 3. 动态规划在其他问题中的应用? 确保回答可靠:基于标准算法知识。</think>### 01背包问题的动态规划实现解析 01背包问题是一个经典的组合优化问题,广泛应用于资源分配、调度等领域。问题描述如下:给定$n$个物品,每个物品$i$有重量$w_i$价值$v_i$,以及一个背包容量$W$。目标是选择物品放入背包,使得总重量不超过$W$,且总价值最大。每个物品只能选择一次(即“0”或“1”)。动态规划(Dynamic Programming, DP)是解决此问题的有效方法,通过避免重复计算子问题实现高效求解[^1][^3]。下面我将逐步解析动态规划的实现方法,包括问题建模、状态定义、状态转移方程、初始化、计算过程,并提供码实现。 --- ### 1. 问题建模动态规划解析 动态规划的核心是将原问题分解为子问题,并存储子问题的解(即“状态”),以避免重复计算。以下是关键步骤: - **定义子问题**:考虑前$i$个物品($i$从1到$n$)在背包容量$j$($j$从0到$W$)时的最大价值。这允许我们逐步构建解。 - **状态表示**:使用二维数组$dp[i][j]$表示前$i$个物品在背包容量$j$时的最大价值。其中: - $i$:物品索引(1 ≤ $i$ ≤ $n$)。 - $j$:背包容量(0 ≤ $j$ ≤ $W$)。 - **状态转移方程**:这是动态规划的核心逻辑。对于每个状态$dp[i][j]$,我们有两种选择: - **不选第$i$个物品**:则最大价值等于前$i-1$个物品在容量$j$时的最大价值,即$dp[i-1][j]$。 - **选第$i$个物品**:仅当$j \geq w_i$时可行,此时最大价值等于$v_i$加上前$i-1$个物品在剩余容量$j - w_i$时的最大价值,即$dp[i-1][j - w_i] + v_i$。 因此,状态转移方程为: $$ dp[i][j] = \begin{cases} \max(dp[i-1][j], dp[i-1][j - w_i] + v_i) & \text{if } j \geq w_i \\ dp[i-1][j] & \text{otherwise} \end{cases} $$ 该方程确保我们总是选择价值最大的方案[^2][^3]。 - **初始化**:边界条件处理: - 当$i=0$(无物品)时,任何容量的最大价值为0:$dp[0][j] = 0$ for all $j \geq 0$。 - 当$j=0$(背包容量为0)时,无法放入任何物品:$dp[i][0] = 0$ for all $i$。 - **计算顺序**:从$i=1$到$n$(遍历物品),对于每个$i$,再从$j=0$到$W$(遍历容量),逐步填充$dp$表。最终,$dp[n][W]$即为最大价值。 - **时间复杂度**:$O(n \times W)$,其中$n$为物品数,$W$为背包容量。空间复杂度为$O(n \times W)$,但可通过滚动数组优化到$O(W)$[^3]。 #### 示例演示 假设物品信息:$n=3$, $W=4$, 重量$w = [1, 2, 3]$, 价值$v = [6, 10, 12]$(索引从1开始)。计算$dp$表: - 初始化:$dp[0][j] = 0$ for all $j$。 - $i=1$(物品1,重量1,价值6): - $j=0$: $dp[1][0] = dp[0][0] = 0$ - $j=1$: $dp[1][1] = \max(dp[0][1], dp[0][0] + 6) = \max(0, 6) = 6$ - $j=2,3,4$: 类似计算,均为6(因为只能选物品1)。 - $i=2$(物品2,重量2,价值10): - $j=2$: $dp[2][2] = \max(dp[1][2], dp[1][0] + 10) = \max(6, 0 + 10) = 10$ - $i=3$(物品3,重量3,价值12): - $j=4$: $dp[3][4] = \max(dp[2][4], dp[2][1] + 12) = \max(16, 6 + 12) = 18$(最终最大价值为18)。 此过程展示了如何通过状态转移逐步推导最优解[^3]。 --- ### 2. 实现方法(Python码) 基于动态规划解析,以下是一个完整的Python实现。码使用二维数组存储状态,并输出最大价值。实现参考了标准动态规划流程[^3][^4]。 ```python def knapsack_01(n, W, w, v): """ 解决01背包问题的动态规划实现 :param n: 物品数量 :param W: 背包容量 :param w: 物品重量列表(索引0为占位,实际从1开始) :param v: 物品价值列表(索引0为占位,实际从1开始) :return: 最大价值 """ # 初始化dp表,大小为(n+1) x (W+1),所有元素为0 dp = [[0] * (W + 1) for _ in range(n + 1)] # 填充dp表:i从1到n,j从0到W for i in range(1, n + 1): for j in range(W + 1): if j >= w[i]: # 当前容量j可以放下物品i dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]) else: # 无法放下物品i dp[i][j] = dp[i-1][j] return dp[n][W] # 最大价值 # 示例输入(物品索引从1开始,w[0]v[0]为占位) n = 3 # 物品数量 W = 4 # 背包容量 w = [0, 1, 2, 3] # 重量数组(索引0占位) v = [0, 6, 10, 12] # 价值数组(索引0占位) max_value = knapsack_01(n, W, w, v) print(f"最大价值为: {max_value}") # 输出: 最大价值为: 18 ``` #### 码说明: - **输入参数**:`n`(物品数)、`W`(背包容量)、`w`(重量列表)、`v`(价值列表)。列表索引从1开始,索引0为占位符,以匹配问题定义[^3]。 - **初始化**:创建二维列表`dp`,所有元素初始化为0,处理边界条件。 - **双重循环**:外层遍历物品(`i`从1到`n`),内层遍历容量(`j`从0到`W`),应用状态转移方程。 - **输出**:返回`dp[n][W]`作为最大价值。 - **时间复杂度**:$O(n \times W)$,适用于中小规模问题。对于大规模$W$,可优化空间复杂度(如使用一维数组)[^3]。 此实现可直接运行,输出正确结果。实际应用中,可扩展以输出具体物品选择方案(通过回溯`dp`表)[^2]。 --- ### 3. 总结 01背包问题的动态规划解法通过定义状态$dp[i][j]$状态转移方程,系统化地求解最大价值。关键优势在于避免了暴力枚举的组合爆炸问题,时间复杂度为$O(n \times W)$。该方法不仅适用于基础背包问题,还可扩展到变体如“采药问题”(时间作为容量)[^4]。动态规划的核心思想——记忆子问题解——在资源优化问题中广泛适用[^1][^2]。 如果您有具体输入数据或需要空间优化版本(使用一维数组),请提供更多细节!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值