
本文深入探讨了在给定预算下最大化收集物品的问题,将其建模为经典的0/1背包问题。文章将详细介绍动态规划(dp)的标准解决方案,包括状态定义、递推关系及具体实现。同时,针对预算值过大导致传统dp效率低下的情况,将提出一种优化策略,通过重新定义dp状态来有效解决。通过本文,读者将全面掌握此类资源分配问题的标准解法及其高级优化技巧。
在许多实际场景中,我们面临着在有限资源(如预算)下最大化收益(如物品数量)的问题。具体而言,假设我们有一个物品列表,每个物品都包含两个属性:购买所需的“金额”和购买后可获得的“数量”。我们还有一个总“预算”。目标是利用这个预算,尽可能多地收集物品。
例如,给定一个物品数组 arr = [[x,y], [x1,y1], ...],其中 x 是金额,y 是物品数量,以及一个总预算 z。我们需要找到一个物品子集,使得这些物品的总金额不超过 z,并且它们总共提供的物品数量最大。
题目中提供了一个初步的尝试代码,该代码首先对物品进行排序:优先按金额升序排列,如果金额相同则按物品数量降序排列。然后,它贪婪地选择物品,只要总金额不超过预算就持续添加。
public static long solve(List<List<Long>> arr, long z) {
arr.sort((a, b) -> {
int z1 = Long.compare(a.get(0) , b.get(0));
if(z1 == 0) {
z1 = Long.compare(b.get(1) , a.get(1)); // 金额相同,按物品数量降序
}
return z1; // 优先按金额升序
});
long totalCost = 0;
long totalItems = 0;
for(List<Long> list : arr) {
if(totalCost + list.get(0) <= z) {
totalCost += list.get(0);
totalItems += list.get(1);
} else {
break; // 预算不足,停止
}
}
return totalItems; // 返回收集到的总物品数
}然而,这种贪婪策略对于此类问题通常不是最优解。考虑一个例子:预算 z = 10,物品 [[7, 10], [3, 4], [3, 5]]。 如果按照上述贪婪策略排序:[[3, 5], [3, 4], [7, 10]]。
但如果选择 [7, 10] 和 [3, 4] (或 [3, 5]),总成本为 7 + 3 = 10,总物品为 10 + 4 = 14 (或 10 + 5 = 15),明显优于贪婪解。这表明该问题本质上是一个经典的0/1背包问题。
0/1背包问题是指:给定一组物品,每件物品都有自己的重量(在这里是金额)和价值(在这里是物品数量),在限定的总重量(在这里是预算)内,选择一部分物品,使得总价值最大化。每个物品只能选择一次(0或1)。
我们可以使用二维动态规划来解决这个问题。 设 dp[i][j] 表示在前 i 个物品中选择,且总金额不超过 j 的情况下,能够获得的最大物品数量。
状态转移方程: 对于第 i 个物品(金额为 cost_i,物品数量为 items_i):
因此,dp[i][j] 取这两种情况的最大值: dp[i][j] = max(dp[i-1][j], dp[i-1][j - cost_i] + items_i) (当 j >= cost_i 时) 如果 j < cost_i,则无法选择第 i 个物品,此时 dp[i][j] = dp[i-1][j]。
基准情况:
import java.util.List;
import java.util.ArrayList;
import java.util.Comparator;
public class KnapsackSolver {
/**
* 使用二维动态规划解决0/1背包问题,最大化收集物品数量。
*
* @param arr 物品列表,每个元素为 [金额, 物品数量]
* @param budget 总预算
* @return 在预算内可收集的最大物品数量
*/
public static long solveWithDP(List<List<Long>> arr, long budget) {
int n = arr.size();
// dp[i][j] 表示考虑前 i 个物品,预算为 j 时能获得的最大物品数量
// 注意:j 的范围是从 0 到 budget
// 如果 budget 很大,这个二维数组会非常大,需要优化
long[][] dp = new long[n + 1][(int) budget + 1];
// 遍历物品
for (int i = 1; i <= n; i++) {
long currentCost = arr.get(i - 1).get(0); // 第 i-1 个物品的金额
long currentItems = arr.get(i - 1).get(1); // 第 i-1 个物品的物品数量
// 遍历预算
for (int j = 0; j <= budget; j++) {
// 不选择当前物品
dp[i][j] = dp[i - 1][j];
// 如果预算足够,尝试选择当前物品
if (j >= currentCost) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][(int) (j - currentCost)] + currentItems);
}
}
}
return dp[n][(int) budget];
}
// 优化:使用一维DP数组,因为dp[i][j]只依赖于dp[i-1][...]
// 为了避免重复选择,内层循环需要从大到小遍历预算
public static long solveWithDP_OptimizedSpace(List<List<Long>> arr, long budget) {
// dp[j] 表示当前预算为 j 时能获得的最大物品数量
// 注意:j 的范围是从 0 到 budget
long[] dp = new long[(int) budget + 1];
// 遍历物品
for (List<Long> item : arr) {
long currentCost = item.get(0);
long currentItems = item.get(1);
// 遍历预算,从大到小,确保每个物品只被考虑一次
for (int j = (int) budget; j >= currentCost; j--) {
dp[j] = Math.max(dp[j], dp[(int) (j - currentCost)] + currentItems);
}
}
return dp[(int) budget];
}
public static void main(String[] args) {
List<List<Long>> items = new ArrayList<>();
items.add(List.of(7L, 10L));
items.add(List.of(3L, 4L));
items.add(List.of(3L, 5L));
long budget = 10;
System.out.println("Max items (2D DP): " + solveWithDP(items, budget)); // 预期输出 15
System.out.println("Max items (1D DP): " + solveWithDP_OptimizedSpace(items, budget)); // 预期输出 15
List<List<Long>> items2 = new ArrayList<>();
items2.add(List.of(10L, 60L));
items2.add(List.of(20L, 100L));
items2.add(List.of(30L, 120L));
long budget2 = 50;
System.out.println("Max items (2D DP, example 2): " + solveWithDP(items2, budget2)); // 预期输出 220
System.out.println("Max items (1D DP, example 2): " + solveWithDP_OptimizedSpace(items2, budget2)); // 预期输出 220
}
}注意事项:
当预算 z(即背包容量)非常大,而物品数量 n 相对较小(例如 n <= 100)时,传统的 O(n * budget) DP 方法不再适用。在这种情况下,我们可以通过重新定义DP状态来优化解决方案。
我们可以将DP状态定义为:dp[v] 表示为了获得总价值 v 所需的最小总金额。
这种方法的关键在于,总物品数量(总价值)通常不会像总预算那样巨大。如果每个物品的物品数量 items_i 最大为 M,物品总数为 N,那么最大总物品数量不会超过 N * M。如果 N * M 远小于 budget,这种方法就非常高效。
首先,我们需要确定所有物品可能达到的最大总物品数量。 maxPossibleItems = sum(items_i for each item)。
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
public class KnapsackSolverLargeBudget {
/**
* 针对大预算值情况的0/1背包问题解法,通过重新定义DP状态。
* dp[v] 存储获得总物品数量 v 所需的最小金额。
*
* @param arr 物品列表,每个元素为 [金额, 物品数量]
* @param budget 总预算
* @return 在预算内可收集的最大物品数量
*/
public static long solveWithLargeBudget(List<List<Long>> arr, long budget) {
// 计算所有物品可能达到的最大总物品数量
long maxTotalItemsPossible = 0;
for (List<Long> item : arr) {
maxTotalItemsPossible += item.get(1);
}
// dp[v] 表示获得总物品数量 v 所需的最小金额
// 初始化为 Long.MAX_VALUE 表示不可达
long[] dp = new long[(int) maxTotalItemsPossible + 1];
Arrays.fill(dp, Long.MAX_VALUE);
dp[0] = 0; // 获得0件物品需要0金额
// 遍历每个物品
for (List<Long> item : arr) {
long currentCost = item.get(0);
long currentItems = item.get(1);
// 从最大可能物品数量开始向下遍历,确保每个物品只被使用一次
for (int v = (int) maxTotalItemsPossible; v >= currentItems; v--) {
if (dp[(int) (v - currentItems)] != Long.MAX_VALUE) { // 如果 (v - currentItems) 状态可达
dp[v] = Math.min(dp[v], dp[(int) (v - currentItems)] + currentCost);
}
}
}
// 找到在预算内可以获得的最大物品数量
long maxItems = 0;
for (int v = (int) maxTotalItemsPossible; v >= 0; v--) {
if (dp[v] <= budget) {
maxItems = v;
break; // 找到第一个符合条件的 v 即为最大值
}
}
return maxItems;
}
public static void main(String[] args) {
List<List<Long>> items = new ArrayList<>();
items.add(List.of(7L, 10L));
items.add(List.of(3L, 4L));
items.add(List.of(3L, 5L));
long budget = 10;
System.out.println("Max items (Large Budget DP): " + solveWithLargeBudget(items, budget)); // 预期输出 15
List<List<Long>> items2 = new ArrayList<>();
items2.add(List.of(10L, 60L));
items2.add(List.of(20L, 100L));
items2.add(List.of(30L, 120L));
long budget2 = 50;
System.out.println("Max items (Large Budget DP, example 2): " + solveWithLargeBudget(items2, budget2)); // 预期输出 220
// 模拟一个大预算场景
List<List<Long>> items3 = new ArrayList<>();
items3.add(List.of(1000000000L, 10L)); // cost 10^9, items 10
items3.add(List.of(1L, 1L)); // cost 1, items 1
items3.add(List.of(2L, 2L)); // cost 2, items 2
long budget3 = 1000000002L; // 10^9 + 2
// 如果使用 O(N*budget) 会爆内存,这里 O(N * maxTotalItemsPossible)
System.out.println("Max items (Large Budget DP, example 3): " + solveWithLargeBudget(items3, budget3)); // 预期输出 13
}
}这种优化策略的时间复杂度是 O(n * maxTotalItemsPossible),空间复杂度是 O(maxTotalItemsPossible)。当 maxTotalItemsPossible 远小于 budget 时,这种方法非常有效。
在处理“给定预算最大化物品收集”这类问题时,我们需要识别其作为0/1背包问题的本质。
选择哪种DP方法取决于具体的问题约束:
理解这些变体和优化对于高效解决实际中的资源分配和优化问题至关重要。
以上就是基于预算限制的最大化物品收集:0/1背包问题的应用与优化的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号