最大化预算内收集物品数量:0/1背包问题的应用与优化

最大化预算内收集物品数量:0/1背包问题的应用与优化

本文深入探讨如何在给定预算下最大化收集物品数量的问题。我们将此问题映射为经典的0/1背包问题,并详细介绍其动态规划解决方案。针对预算过大导致传统dp效率低下的情况,文章还将介绍一种通过重新定义dp状态来优化的方法,并提供相应的代码示例,旨在帮助读者理解并掌握解决此类资源分配问题的专业策略。

问题描述

假设我们有一个物品列表,每个物品都由两个属性定义:购买所需的“金额”(或成本)和购买后能获得的“物品数量”(或价值)。我们还有一个总的“预算”限制。目标是在不超过预算的前提下,最大化我们能收集到的总物品数量。

例如,给定一个数组 arr = [[x,y], [x1,y1], …],其中 x 是金额,y 是物品数量,以及一个预算 z。我们需要找到一个子集,使得所有选定物品的金额之和不超过 z,且所有选定物品的数量之和最大。

初始贪心尝试及其局限性

在解决这类问题时,一种直观的尝试是采用贪心策略。例如,可以先对物品进行排序,优先选择金额较小的物品,或者在金额相同时优先选择物品数量较多的。原始代码中展示了这种尝试:

public static long solve(List<List> 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 item : arr) {        long cost = item.get(0);        long items = item.get(1);        if(totalCost + cost <= z) {            totalCost += cost;            totalItems += items;        } else {            break; // 预算不足,停止        }    }    return totalItems;}

这种贪心策略在某些特定问题(如分数背包问题)中是有效的,但对于0/1背包问题(每个物品只能选择一次,不能分割),它并不能保证找到最优解。例如,如果存在一个金额略大但物品数量极多的物品,贪心策略可能会因为优先选择小金额物品而错过这个最优选择。因此,我们需要更强大的方法来解决。

0/1背包问题的动态规划解法

此问题是经典的0/1背包问题的一个变体:

每个物品的“金额”对应背包问题的“重量”。每个物品的“物品数量”对应背包问题的“价值”。总“预算”对应背包问题的“背包容量”。

动态规划是解决0/1背包问题的标准方法。

1. 定义DP状态

我们定义 dp[w] 为在不超过预算 w 的情况下,能收集到的最大物品数量。

无限画 无限画

千库网旗下AI绘画创作平台

无限画 467 查看详情 无限画

2. 状态转移方程

遍历每个物品。对于当前物品 i,其金额为 cost_i,物品数量为 items_i。对于每个可能的预算 w(从 z 递减到 cost_i),我们可以选择两种策略:

不选择物品 i: 此时最大物品数量仍为 dp[w]。选择物品 i: 此时最大物品数量为 dp[w – cost_i] + items_i。

因此,状态转移方程为:dp[w] = max(dp[w], dp[w – cost_i] + items_i)

需要注意的是,内层循环 w 必须从大到小遍历,以确保每个物品只被选择一次(0/1性质)。

3. 初始化

dp[0] = 0 (预算为0时,能收集0个物品)。所有其他 dp[w] 初始化为0。

4. 示例代码(Java)

import java.util.List;import java.util.ArrayList;import java.util.Arrays;public class MaximizeItemsWithBudget {    /**     * 使用标准0/1背包动态规划解决问题。     *     * @param arr 物品列表,每个元素 [金额, 物品数量]     * @param budget 总预算     * @return 能收集到的最大物品数量     */    public static long solveKnapsackDP(List<List> arr, long budget) {        // dp[w] 表示在预算为 w 时,能收集到的最大物品数量        // 注意:如果 budget 很大,这个数组会非常大,可能导致内存溢出或计算时间过长。        // long[] dp = new long[(int) (budget + 1)]; // 预算可能超过 int 范围,需要注意类型转换        // 鉴于 budget 可以是 long,这里需要考虑实际的 budget 范围。        // 假设 budget 在 int 范围内,或者我们使用 HashMap 来模拟稀疏数组。        // 为了演示,我们假设 budget 能够被 int 强制转换且在合理范围内。        // 如果 budget 真的非常大,请参考下面的“处理大预算”部分。        if (budget > Integer.MAX_VALUE) {            // 提示:预算过大,请考虑使用优化方法            System.err.println("Warning: Budget is too large for standard DP array. Consider optimized approach.");            // 这里可以抛出异常或调用优化方法            // For now, we will proceed with a smaller assumed budget for demonstration.            // In a real scenario, this would be a critical check.            // For this example, let's cap budget for array size, or use alternative DP if needed.            // If budget is truly large, the below array initialization will fail.            // We'll proceed with the assumption that budget fits into int for array indexing,            // or that the "large budget" case is handled by the next section.            // For the sake of a runnable example, let's assume budget is within int max for array size.            // Or more practically, use the optimized approach for large budgets.            // For a general tutorial, it's crucial to point this out.            // Let's use a smaller max budget for this example to avoid runtime errors            // but emphasize the limitation.            // Let's cap budget to a reasonable int for the array size for demonstration.            // If budget exceeds this, the optimized approach is necessary.            // For this example, let's assume budget <= 10^5 or similar.            // If budget is larger, the `dp` array will be too big.        }        // 假设 budget 不会超过 Integer.MAX_VALUE / 2,以避免数组过大        // 在实际应用中,如果 budget 真的很大,需要使用下面的优化方法        int maxBudgetForArray = (int) Math.min(budget, 1_000_000); // 示例限制,实际应根据内存决定        long[] dp = new long[maxBudgetForArray + 1];        for (List item : arr) {            long cost = item.get(0);            long items = item.get(1);            // 从后往前遍历,确保每个物品只被选择一次            for (int w = maxBudgetForArray; w >= cost; w--) {                dp[w] = Math.max(dp[w], dp[(int)(w - cost)] + items);            }        }        return dp[maxBudgetForArray]; // 返回最大预算下的最大物品数量    }    public static void main(String[] args) {        List<List> items = new ArrayList();        items.add(Arrays.asList(10L, 60L)); // cost, items        items.add(Arrays.asList(20L, 100L));        items.add(Arrays.asList(30L, 120L));        long budget = 50;        long maxItems = solveKnapsackDP(items, budget);        System.out.println("Max items with budget " + budget + " (standard DP): " + maxItems); // Expected: 220 (20+100, 30+120 -> 50, 220)        // Example with large budget (will trigger warning/limitation in current implementation)        // For actual large budget, the optimized approach below is needed.        // long largeBudget = 1_000_000_000L;        // long maxItemsLargeBudget = solveKnapsackDP(items, largeBudget);        // System.out.println("Max items with large budget (standard DP): " + maxItemsLargeBudget);    }}

处理大预算(大重量)的情况

当预算 z(即背包容量)非常大时,例如达到 10^9 甚至 10^12,而物品数量 N 相对较小(例如 N <= 100 或 N <= 200),标准0/1背包的 dp 数组大小会变得无法接受 (O(N * Z) 的时间和空间复杂度)。

在这种情况下,我们可以重新定义DP状态。由于物品数量 N 较小,而每个物品的“物品数量”或“价值”通常也在一个有限的范围内,我们可以将DP状态定义为:

1. 定义DP状态(优化版)

dp[v] 表示为了获得总价值(物品数量) v 所需的最小金额。

2. 状态转移方程

遍历每个物品。对于当前物品 i,其金额为 cost_i,物品数量为 items_i。对于每个可能的总价值 v(从 maxTotalItems 递减到 items_i),我们可以选择两种策略:

不选择物品 i: 此时所需最小金额仍为 dp[v]。选择物品 i: 此时所需最小金额为 dp[v – items_i] + cost_i。

因此,状态转移方程为:dp[v] = min(dp[v], dp[v – items_i] + cost_i)

3. 初始化

dp[0] = 0 (获得0个物品需要0金额)。所有其他 dp[v] 初始化为一个足够大的值(例如 Long.MAX_VALUE),表示无法达到该价值。

4. 计算最大总物品数量

首先需要计算所有物品可能达到的最大总物品数量 maxPossibleItems。然后,在填充完 dp 数组后,从 maxPossibleItems 倒序遍历 v,找到第一个 v 使得 dp[v] <= budget。这个 v 就是在给定预算下能获得的最大物品数量。

5. 示例代码(Java)

import java.util.List;import java.util.ArrayList;import java.util.Arrays;public class MaximizeItemsWithBudgetOptimized {    /**     * 当预算非常大时,使用优化后的0/1背包动态规划解决问题。     * DP状态定义为:dp[v] = 获得总价值 v 所需的最小金额。     *     * @param arr 物品列表,每个元素 [金额, 物品数量]     * @param budget 总预算     * @return 能收集到的最大物品数量     */    public static long solveKnapsackOptimized(List<List> arr, long budget) {        long maxPossibleItems = 0;        for (List item : arr) {            maxPossibleItems += item.get(1); // 累加所有物品的最大数量        }        // dp[v] 存储获得总价值 v 所需的最小金额        // 数组大小取决于 maxPossibleItems,通常比 budget 小很多        long[] dp = new long[(int) (maxPossibleItems + 1)];        // 初始化:获得0价值需要0金额,其他价值初始化为无穷大        Arrays.fill(dp, Long.MAX_VALUE);        dp[0] = 0;        for (List item : arr) {            long cost = item.get(0);            long items = item.get(1);            // 从后往前遍历,确保每个物品只被选择一次            for (int v = (int) maxPossibleItems; v >= items; v--) {                if (dp[(int)(v - items)] != Long.MAX_VALUE) { // 确保 (v - items) 是可达的                    dp[v] = Math.min(dp[v], dp[(int)(v - items)] + cost);                }            }        }        // 从最大可能的物品数量开始倒序查找,找到第一个满足预算条件的价值        long resultMaxItems = 0;        for (int v = (int) maxPossibleItems; v >= 0; v--) {            if (dp[v] <= budget) {                resultMaxItems = v;                break;            }        }        return resultMaxItems;    }    public static void main(String[] args) {        List<List> items = new ArrayList();        items.add(Arrays.asList(10L, 60L));        items.add(Arrays.asList(20L, 100L));        items.add(Arrays.asList(30L, 120L));        long budget = 50;        long maxItemsOptimized = solveKnapsackOptimized(items, budget);        System.out.println("Max items with budget " + budget + " (optimized DP): " + maxItemsOptimized); // Expected: 220        // 模拟一个大预算场景,优化方法在这种情况下更有效        long largeBudget = 1_000_000_000L; // 10亿        long maxItemsLargeBudgetOptimized = solveKnapsackOptimized(items, largeBudget);        System.out.println("Max items with large budget " + largeBudget + " (optimized DP): " + maxItemsLargeBudgetOptimized); // Expected: 280 (所有物品都买得起 60+100+120)        // 另一个例子        List<List> items2 = new ArrayList();        items2.add(Arrays.asList(1L, 10L));        items2.add(Arrays.asList(2L, 20L));        items2.add(Arrays.asList(3L, 30L));        long budget2 = 4L; // 预算4        // 理论上,我们可以选择 (1,10) + (3,30) -> cost 4, items 40        // 或者 (1,10) + (2,20) -> cost 3, items 30        // 或者 (2,20) + (3,30) -> cost 5, items 50 (超预算)        // 应该选择 (1,10) + (3,30) 得到 40        long maxItems2 = solveKnapsackOptimized(items2, budget2);        System.out.println("Max items with budget " + budget2 + " (optimized DP): " + maxItems2); // Expected: 40    }}

总结

在预算内最大化收集物品数量的问题是经典的0/1背包问题的一个直接应用。

标准动态规划: 当预算(背包容量)相对较小,且物品数量不是特别大时,可以使用 dp[w] 表示在预算 w 下能获得的最大物品数量。其时间复杂度为 O(N * Z),其中 N 是物品数量,Z 是预算。优化动态规划: 当预算 Z 非常大,但物品数量 N 和总物品价值(或数量)相对较小时,可以采用 dp[v] 表示获得总价值 v 所需的最小金额。这种方法的复杂度为 O(N * V_total),其中 V_total 是所有物品的最大可能总价值。这种方法在 Z 极大时能显著提高效率。

选择哪种动态规划方法取决于问题的具体约束:是预算 Z 还是总价值 V_total 更小。理解这两种DP状态定义及其适用场景是解决此类优化问题的关键。

以上就是最大化预算内收集物品数量:0/1背包问题的应用与优化的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/871792.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
360页游官网服务入口在哪_360页游官网服务地址入口
上一篇 2025年11月28日 05:29:03
天书系统终极指南:解锁角色战斗力的隐藏宝藏
下一篇 2025年11月28日 05:29:06

相关推荐

  • 修复Django电商项目中AJAX过滤产品列表图片不显示问题

    在Django电商项目中,当使用AJAX动态加载过滤后的产品列表时,常遇到图片无法正常显示的问题。这通常是由于前端模板中图片加载方式(如data-setbg属性结合JavaScript库)与AJAX动态内容更新机制不兼容所致。解决方案是直接在AJAX返回的HTML中使用标准的标签来渲染图片,确保浏览…

    2026年5月10日
    000
  • Matplotlib 地图中多类型图例的创建与优化

    Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化

    本教程旨在解决matplotlib地图可视化中,如何在一个图例中同时展示颜色块(如区域分类)和自定义标记(如特定兴趣点)的问题。文章详细介绍了当传统`patch`对象无法正确显示标记时,如何利用`matplotlib.lines.line2d`创建标记图例句柄,并将其与颜色块图例句柄合并,从而生成一…

    2026年5月10日 用户投稿
    100
  • Golang JSON序列化:控制敏感字段暴露的最佳实践

    本教程探讨golang中如何高效控制结构体字段在json序列化时的可见性。当需要将包含敏感信息的结构体数组转换为json响应时,通过利用`encoding/json`包提供的结构体标签,特别是`json:”-“`,可以轻松实现对特定字段的忽略,从而避免敏感数据泄露,确保api…

    2026年5月10日
    000
  • 比特币新手教程 比特币交易平台有哪些

    比特币是一种去中心化的数字货币,基于区块链技术实现点对点交易,具有匿名性、有限发行和不可篡改等特点;新手可通过交易所购买,P2P交易获得比特币,常用平台包括Binance、OKX和Huobi;交易流程包括注册账户、实名认证、绑定支付方式、充值法币并下单购买,可选择市价单或限价单;比特币存储方式有交易…

    2026年5月10日
    000
  • c++中的SFINAE技术是什么_c++模板编程中的SFINAE原理与应用

    SFINAE 是“替换失败不是错误”的原则,指模板实例化时若参数替换导致错误,只要存在其他合法候选,编译器不报错而是继续重载决议。它用于条件启用模板、类型检测等场景,如通过 decltype 或 enable_if 控制函数重载,实现类型特征判断。尽管 C++20 引入 Concepts 简化了部分…

    2026年5月10日
    000
  • Go语言mgo查询构建:深入理解bson.M与日期范围查询的正确实践

    本文旨在解决go语言mgo库中构建复杂查询时,特别是涉及嵌套`bson.m`和日期范围筛选的常见错误。我们将深入剖析`bson.m`的类型特性,解释为何直接索引`interface{}`会导致“invalid operation”错误,并提供一种推荐的、结构清晰的代码重构方案,以确保查询条件能够正确…

    2026年5月10日
    100
  • 修复点击时按钮抖动:CSS垂直对齐实践

    本文探讨了在Web开发中,交互式按钮(如播放/暂停按钮)在点击时发生意外垂直位移的问题。通过分析CSS样式变化对元素布局的影响,我们发现这是由于按钮不同状态下的边框样式和内边距改变,以及默认的垂直对齐行为共同作用所致。核心解决方案是利用CSS的vertical-align属性,将其设置为middle…

    2026年5月10日
    100
  • Golang goroutine与channel调试技巧

    使用go run -race检测数据竞争,结合runtime.NumGoroutine监控协程数量,通过pprof分析阻塞调用栈,利用select超时避免永久阻塞,有效排查goroutine泄漏、死锁和数据竞争问题。 Go语言的goroutine和channel是并发编程的核心,但它们也带来了调试上…

    2026年5月10日
    000
  • 使用 Jupyter Notebook 进行探索性数据分析

    Jupyter Notebook通过单元格实现代码与Markdown结合,支持数据导入(pandas)、清洗(fillna)、探索(matplotlib/seaborn可视化)、统计分析(describe/corr)和特征工程,便于记录与分享分析过程。 Jupyter Notebook 是进行探索性…

    2026年5月10日
    000
  • 《魔兽世界》将于6月11日开启国服回归技术测试

    《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试

    《%ign%ignore_a_1%re_a_1%》官方宣布,将于6月11日开启国服回归技术测试,时间为7天,并称可以在6月内正式开服,玩家们可以访问官网下载战网客户端并预下载“巫妖王之怒”客户端,技术测试详情见下图。 WordAi WordAI是一个AI驱动的内容重写平台 53 查看详情 以上就是《…

    2026年5月10日 用户投稿
    200
  • 如何在HTML中插入表单元素_HTML表单控件与输入类型使用指南

    HTML表单通过标签构建,包含action和method属性定义数据提交目标与方式,常用input类型如text、password、email等适配不同输入需求,配合label、required、placeholder提升可用性,结合textarea、select、button等控件实现完整交互,是…

    2026年5月10日
    100
  • 前端缓存策略与JavaScript存储管理

    根据数据特性选择合适的存储方式并制定清晰的读写与清理逻辑,能显著提升前端性能;合理运用Cookie、localStorage、sessionStorage、IndexedDB及Cache API,结合缓存策略与定期清理机制,可在保证用户体验的同时避免安全与性能隐患。 前端缓存和JavaScript存…

    2026年5月10日
    200
  • HTML5网页如何实现手势操作 HTML5网页移动端交互的处理技巧

    首先利用原生touch事件实现滑动判断,再通过preventDefault解决滚动冲突,接着引入Hammer.js处理复杂手势,最后通过优化点击区域、避免事件冲突和增加视觉反馈提升体验。 在移动端浏览器中,HTML5网页可以通过触摸事件实现手势操作,提升用户体验。虽然原生JavaScript提供了基…

    2026年5月10日
    000
  • 创建指定大小并填充特定数据的Golang文件教程

    本文将介绍如何使用Golang创建一个指定大小的文件,并用特定数据填充它。我们将使用 `os` 包提供的函数来创建和截断文件,从而实现快速生成大文件的目的。示例代码展示了如何创建一个10MB的文件,并将其填充为全零数据。掌握这些方法,可以方便地在例如日志系统或磁盘队列等场景中,预先创建测试文件或初始…

    2026年5月10日
    000
  • 深入理解 Express.js 中 next() 参数的作用与中间件机制

    本文深入探讨 express.js 中间件函数中的 `next()` 参数。它负责将控制权传递给请求-响应周期中的下一个中间件或路由处理程序。文章将详细解释 `next()` 的工作原理、中间件的注册与执行顺序,以及不正确使用 `next()` 可能导致请求挂起的风险,并通过代码示例和实际应用场景,…

    2026年5月10日
    000
  • Python命令怎样使用profile分析脚本性能 Python命令性能分析的基础教程

    使用Python的cProfile模块分析脚本性能最直接的方式是通过命令行执行python -m cProfile your_script.py,它会输出每个函数的调用次数、总耗时、累积耗时等关键指标,帮助定位性能瓶颈;为进一步分析,可将结果保存为文件python -m cProfile -o ou…

    2026年5月10日
    000
  • 使用 WebCodecs VideoDecoder 实现精确逐帧回退

    本文档旨在解决在使用 WebCodecs VideoDecoder 进行视频解码时,实现精确逐帧回退的问题。通过比较帧的时间戳与目标帧的时间戳,可以避免渲染中间帧,从而提高用户体验。本文将提供详细的解决方案和示例代码,帮助开发者实现精确的视频帧控制。 在使用 WebCodecs VideoDecod…

    2026年5月10日
    000
  • 如何插入查询结果数据_SQL插入Select查询结果方法

    如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法

    使用INSERT INTO…SELECT语句可高效插入数据,通过NOT EXISTS、LEFT JOIN、MERGE语句或唯一约束避免重复;表结构不一致时可通过别名、类型转换、默认值或计算字段处理;结合存储过程可提升可维护性,支持参数化与动态SQL。 将查询结果数据插入到另一个表中,可以…

    2026年5月10日 用户投稿
    000
  • Discord.py 交互按钮超时与持久化解决方案

    本教程旨在解决Discord.py中交互按钮在一段时间后出现“This Interaction Failed”错误的问题。我们将深入探讨视图(View)的超时机制,并提供通过正确设置timeout参数以及利用bot.add_view()方法实现按钮持久化的具体方案,确保您的机器人交互功能稳定可靠,即…

    2026年5月10日
    000
  • Debian Copilot的社区活跃度如何

    debian copilot是codeberg社区维护的ai助手,旨在为debian用户提供服务。尽管搜索结果中没有直接提供关于debian copilot社区支持活跃度的具体数据,但我们可以通过debian社区的整体活跃度和特点来推断其活跃性。 Debian社区的一般情况: Debian拥有详尽的…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信