洛谷 P1776 & HDU 2191 题解(单调队列优化多重背包)

本博客探讨了如何使用单调队列优化来解决多重背包问题,通过分析f[i][j]的状态转移,揭示了在某些情况下可以利用滑动窗口和单调队列减少计算复杂度,从而求解价值最大的物品组合。文章提供了具体的思路分析和代码实现细节。

题意简述

(按洛谷,HDU的输入顺序略有不同,还多组数据,较为毒瘤)

多重背包问题。给n,m,分别表示物品数量,背包总容积,每个物品依次给出3个属性w,v,c,表示物品的价值,重量,能选多少个。请在背包不爆炸的情况下选价值最大。

数据

输入:
4 20
3 9 3
5 9 1
9 4 2
8 1 3
输出:
47

思路

在朴素的多重背包中,我们设f[i][j]f[i][j]f[i][j]为考虑前iii个物品,然后背包容量为jjj时的最优解。显然有f[i][j]=max{ f[i−1][j−k∗v[i]]+k∗w[i]}f[i][j]=max\{ f[i-1][j-k*v[i]]+k*w[i]\}f[i][j]=max{ f[i1][jkv[i]]+kw[i]},其中0&lt;=k&lt;=min{ c[i],m/v[i]}0&lt;=k&lt;=min\{c[i],m/v[i]\}0<=k<=min{ c[i],m/v[i]}。(注释:c[i]c[i]c[i]为当前物品能选多少个,m/v[i]m/v[i]m/v[i]是由于容量限制导致当前物品最多就能选m/v[i]m/v[i]m/v[i]个,其中///C++C++C++中的用法,表示下取整的除)。

我们会发现,这样高的时间复杂度过不去这个题目。那么,我们仔细观察一下fff的变化。

假如此时n=100000,m&gt;9×v[i]n=100000,m&gt;9\times v[i]n=100000,m>9×v[i]设我们现在正在枚举iii,然后这个物品iii能选222个,将w[i],v[i]w[i],v[i]w[i],v[i]简记为w,vw,vw,v,将f[i−1][j]f[i-1][j]f[i1][j]简记为g(j)g(j)g(j)。然后开始第二层枚举jjj

j=6v,f[i][j]=max{ g(6v),g(5v)+w,g(4v)+2w}j=5v,f[i][j]=max{

&lt;think&gt;好的,我现在需要解析用户提供的关于C语言实现装载问题的分支限界法代码。用户提到了结构体node,队列操作如EnQueue、DeQueue,以及变量bestw、weight、level、parent。首先,我得回忆一下装载问题的基本概念和分支限界法的应用。 装载问题通常是关于在有限容量的货船上尽可能装载更多货物,分为0-1背包问题和部分背包问题。这里应该是0-1的情况,即每个货物要么装要么不装。分支限界法是一种通过广度优先或最小耗费优先来遍历解空间树的算法,使用剪枝策略避免无效搜索,提高效率。 接下来,结构体node可能代表队列中的节点,每个节点需要保存当前节点的重量、层级、父节点等信息。bestw用于记录当前最优解,weight数组存储各货物重量,level表示当前处理的货物层级,parent可能用于回溯路径。 队列操作EnQueue和DeQueue用于管理活节点,分支限界法通常使用优先队列,但可能这里用的是普通队列。需要确定队列中节点的出队顺序,是FIFO还是优先级。如果是FIFO,那就是队列式分支限界法;如果是优先队列,可能基于当前重量排序,优先处理更有希望的节点。 然后,代码的大致流程可能是初始化队列,将根节点加入队列。循环处理队列中的每个节点,扩展其子节点(装或不装当前货物),计算可能的重量,更新bestw,并将可行的子节点入队。剪枝条件是不超过货船容量,并且可能的上界是否优于当前bestw。 需要检查每个节点的处理是否正确,特别是子节点的生成和剪枝条件。例如,当处理到某个level时,计算左子节点(装该货物)的重量,如果不超过容量则入队,并更新bestw。右子节点(不装)可能需要计算剩余货物可能的重量上界,如果上界大于bestw才入队,否则剪枝。 可能存在的问题包括队列的实现是否正确,节点信息的保存是否遗漏,bestw的更新时机是否正确,以及剪枝条件是否合理。此外,父节点的记录是否用于最后构造最优解的路径,但用户可能只需要最大重量,所以可能不需要记录父节点。 需要进一步考虑代码的具体实现,比如结构体node的定义是否包含当前重量、层级、父节点指针等信息。队列的操作是否正确处理了节点的入队和出队,避免内存泄漏或队列溢出。 此外,正确性验证方面,需要测试不同输入情况,比如所有货物都能装入,或部分装入的情况,确保bestw的正确性。性能方面,分支限界法相对于回溯法应该更高效,因为剪枝更积极。 最后,用户可能需要了解代码的具体部分,比如结构体成员的作用,队列操作如何影响算法流程,变量如何更新等。需要分步骤解释代码逻辑,可能结合伪代码或流程图来说明。&lt;/think&gt;### C语言实现装载问题的分支限界法代码解析 #### 1. 数据结构定义 ```c typedef struct node { int weight; // 当前节点累计重量 int level; // 当前处理的货物层级(索引) struct node* parent; // 父节点指针(用于回溯路径) } Node; Queue* queue; // 活节点队列 int bestw = 0; // 最优解重量 int* weights; // 货物重量数组 int capacity; // 货船容量 ``` #### 2. 算法流程 1. **初始化队列**:创建根节点(weight=0, level=-1)并入队 2. **循环处理队列**: ```c while (!isEmpty(queue)) { Node* current = DeQueue(queue); int currentLevel = current-&gt;level + 1; // 左子树(装载当前货物) int leftWeight = current-&gt;weight + weights[currentLevel]; if (leftWeight &lt;= capacity) { if (leftWeight &gt; bestw) bestw = leftWeight; if (currentLevel &lt; n-1) // 非叶子节点则继续扩展 EnQueue(createNode(leftWeight, currentLevel, current)); } // 右子树(不装载当前货物) int upperBound = current-&gt;weight + remainingWeights(currentLevel); if (upperBound &gt; bestw &amp;&amp; currentLevel &lt; n-1) { EnQueue(createNode(current-&gt;weight, currentLevel, current)); } } ``` #### 3. 关键点解析 - **剪枝策略**:右子树仅当剩余货物总重量(`remainingWeights`)能使当前解超过`bestw`时才扩展[^1] - **队列选择**:使用FIFO队列实现广度优先搜索 - **时间复杂度**:最坏情况$O(2^n)$,但剪枝可显著提升效率 - **空间复杂度**:$O(b^d)$(b为分支因子,d为最大深度) #### 4. 示例说明 假设货物重量$[3,5,2]$,容量$10$: 1. 根节点(0, -1)入队 2. 处理层级0: - 左分支3(&le;10,更新bestw) - 右分支剩余8(5+2=7 &gt;0),保留 3. 处理层级1: - 左分支3+5=8(更新bestw) - 右分支3+剩余2=5(&gt;当前bestw=8? 不成立,剪枝)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值