📅  最后修改于: 2023-12-03 14:49:55.464000             🧑  作者: Mango
在算法竞赛中,01 背包问题是一个经典问题。给定一组物品,每个物品有一个重量和一个价值,现在需要从中选择一些物品放入一个容量为 $V$ 的背包中,使得选中的物品重量不超过 $V$,而且价值最大。
最小计数分支和界限(MCKP)算法是经典的 01 背包问题的一种求解方法。它将 01 背包问题转化为具有多个约束的最小费用流问题,并利用动态规划的思想解决。
MCKP 算法的核心是计算界限(Lower Bound),它可以帮助我们确定一个上限,从而剪枝掉一些不必要的搜索分支。具体来说,MCKP 算法会将物品按照单位重量价值(价值除以重量)从大到小排序,然后从前往后依次选择物品。如果一个物品不能被完整地放入背包中,我们就尽可能多地放入,直到背包被填满为止。这个过程中选择的物品和它们的部分重量构成了一个上界,我们将这个上界称为界限。
通过计算每个物品被选择的概率和以当前状态为前缀的最大价值,我们可以得到一个下界。这个下界是所有可能的选择中的最大值。如果界限小于当前最优解,我们就可以停止往下搜索了。
代码实现:
def lower_bound():
"""
计算当前状态的界限
:return: 当前状态的界限
"""
bound = sum(p[v] for v in range(n) if x[v] < c[v]) # 只计算没选满的物品的价值
cur_wt = sum(x[v] * w[v] for v in range(n)) # 当前背包的重量
for v in range(n):
if x[v] == c[v]:
continue
wt = cur_wt + (c[v] - x[v]) * w[v] # 加入该物品剩余部分的重量
if wt > W:
bound += (W - cur_wt) * p[v] / w[v] # 超过背包容量,按比例添加
break
bound += c[v] * p[v]
cur_wt = wt
return bound
接下来,我们只需要在搜索过程中维护一个最小界限即可。如果下一个状态的界限小于当前最优解,则可以将其剪枝掉。
完整代码实现:
def mckp(i, value, weight, count):
"""
从第 i 个物品开始做决策,当前状态的价值为 value,重量为 weight,个数为 count
:param i: 从第 i 个物品开始做决策
:param value: 当前状态的价值
:param weight: 当前状态的重量
:param count: 当前状态的个数
:return: 从第 i 个物品开始的子问题的最优解
"""
nonlocal ans
if weight > W or i == n: # 超过背包容量或遍历完所有物品,更新答案
if value > ans:
ans = value
return
if lb + value <= ans: # 当前状态的界限小于上界,剪枝
return
if count == c[i]: # 当前物品已经选满,直接跳过
mckp(i + 1, value, weight, 0)
return
for t in range(c[i] - count + 1): # 枚举选择数量
x[i] = count + t
if weight + t * w[i] > W: # 超过背包容量,跳过后面的选择
break
lb = lower_bound()
mckp(i + 1, value + t * p[i], weight + t * w[i], count + t)
最小计数分支和界限算法是一种优秀的求解 01 背包问题的方法。它通过计算每个状态的界限剪枝掉不必要的搜索分支,从而大大提高了搜索效率。同时,它的实现也很简单,只需要在动态规划的框架下加入界限计算和剪枝即可。