最大化预算内收集物品数量: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)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月28日 05:28:51
下一篇 2025年11月28日 05:29:16

相关推荐

  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 旋转长方形后,如何计算其相对于画布左上角的轴距?

    绘制长方形并旋转,计算旋转后轴距 在拥有 1920×1080 画布中,放置一个宽高为 200×20 的长方形,其坐标位于 (100, 100)。当以任意角度旋转长方形时,如何计算它相对于画布左上角的 x、y 轴距? 以下代码提供了一个计算旋转后长方形轴距的解决方案: const x = 200;co…

    2025年12月24日
    000
  • 旋转长方形后,如何计算它与画布左上角的xy轴距?

    旋转后长方形在画布上的xy轴距计算 在画布中添加一个长方形,并将其旋转任意角度,如何计算旋转后的长方形与画布左上角之间的xy轴距? 问题分解: 要计算旋转后长方形的xy轴距,需要考虑旋转对长方形宽高和位置的影响。首先,旋转会改变长方形的长和宽,其次,旋转会改变长方形的中心点位置。 求解方法: 计算旋…

    2025年12月24日
    000
  • 旋转长方形后如何计算其在画布上的轴距?

    旋转长方形后计算轴距 假设长方形的宽、高分别为 200 和 20,初始坐标为 (100, 100),我们将它旋转一个任意角度。根据旋转矩阵公式,旋转后的新坐标 (x’, y’) 可以通过以下公式计算: x’ = x * cos(θ) – y * sin(θ)y’ = x * …

    2025年12月24日
    000
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 如何计算旋转后长方形在画布上的轴距?

    旋转后长方形与画布轴距计算 在给定的画布中,有一个长方形,在随机旋转一定角度后,如何计算其在画布上的轴距,即距离左上角的距离? 以下提供一种计算长方形相对于画布左上角的新轴距的方法: const x = 200; // 初始 x 坐标const y = 90; // 初始 y 坐标const w =…

    2025年12月24日
    200
  • CSS元素设置em和transition后,为何载入页面无放大效果?

    css元素设置em和transition后,为何载入无放大效果 很多开发者在设置了em和transition后,却发现元素载入页面时无放大效果。本文将解答这一问题。 原问题:在视频演示中,将元素设置如下,载入页面会有放大效果。然而,在个人尝试中,并未出现该效果。这是由于macos和windows系统…

    2025年12月24日
    200
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 如何计算旋转后的长方形在画布上的 XY 轴距?

    旋转长方形后计算其画布xy轴距 在创建的画布上添加了一个长方形,并提供其宽、高和初始坐标。为了视觉化旋转效果,还提供了一些旋转特定角度后的图片。 问题是如何计算任意角度旋转后,这个长方形的xy轴距。这涉及到使用三角学来计算旋转后的坐标。 以下是一个 javascript 代码示例,用于计算旋转后长方…

    2025年12月24日
    000
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000
  • 如何用前端实现 Windows 10 设置界面的鼠标移动探照灯效果?

    如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…

    2025年12月24日
    000
  • 使用CSS mask属性指定图片URL时,为什么浏览器无法加载图片?

    css mask属性未能加载图片的解决方法 使用css mask属性指定图片url时,如示例中所示: mask: url(“https://api.iconify.design/mdi:apple-icloud.svg”) center / contain no-repeat; 但是,在网络面板中却…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信