You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询:带类别必选约束的背包问题对应何种已知变体及实现方案?

问题对应的背包变体及解决方案

你遇到的这个问题是分组背包问题(Group Knapsack Problem)的约束强化版——普通分组背包允许每个类别(组)选择0个或1个物品,但你的场景要求每个类别必须恰好选择一个物品,我们可以称它为「强制选择型分组背包问题」。

核心解法思路

这类问题最适合用动态规划(DP)解决,核心逻辑是在普通分组背包的基础上调整状态转移规则:

  1. 定义DP状态:dp[i][w] 表示考虑前i个类别,总重量不超过w时能获得的最大收益;用负无穷标记「不可行状态」(比如前i个类别无法凑出总重量w的情况)。
  2. 初始化状态:第一个类别必须选一个物品,所以遍历第一个类别的所有物品,将重量≤Wmax的物品对应的dp[1][wj]设为该物品的收益。
  3. 状态转移:对于第i个类别的每个物品(重量wj,收益pj),遍历所有可能的重量w,如果dp[i-1][w - wj]是可行状态(非负无穷),则更新dp[i][w] = max(dp[i][w], dp[i-1][w - wj] + pj)。
  4. 结果提取:dp[总类别数][Wmax]就是满足条件的最大收益,还可以通过回溯DP数组找到具体选中的物品。

代码实现(Python)

我们用你给出的示例来验证代码逻辑:

def forced_group_knapsack(Wmax, groups):
    num_groups = len(groups)
    # 初始化DP数组,负无穷代表该状态不可行
    dp = [[-float('inf')] * (Wmax + 1) for _ in range(num_groups + 1)]
    
    # 处理第一个分组:必须选一个物品
    first_group = groups[0]
    for wj, pj in first_group:
        if wj <= Wmax:
            dp[1][wj] = max(dp[1][wj], pj)
    
    # 处理后续分组
    for i in range(2, num_groups + 1):
        current_group = groups[i-1]
        # 遍历所有可能的重量状态
        for w in range(Wmax + 1):
            # 只有上一组的状态可行时,才能基于它更新当前组
            if dp[i-1][w] != -float('inf'):
                for wj, pj in current_group:
                    if w + wj <= Wmax:
                        if dp[i][w + wj] < dp[i-1][w] + pj:
                            dp[i][w + wj] = dp[i-1][w] + pj
    
    # 找到最大收益
    max_profit = max(dp[num_groups])
    # 回溯选中的物品(可选步骤)
    selected_items = []
    current_weight = Wmax
    for i in range(num_groups, 0, -1):
        current_group = groups[i-1]
        for wj, pj in current_group:
            if current_weight >= wj and dp[i][current_weight] == dp[i-1][current_weight - wj] + pj:
                selected_items.append((wj, pj))
                current_weight -= wj
                break
    selected_items.reverse()
    
    return max_profit, selected_items

# 示例输入
Wmax = 400
# 每个子列表代表一个类别,元素为(重量, 收益)
groups = [
    [(500, 25), (150, 5)],   # 书籍类:《圣经》《小王子》
    [(80, 120), (250, 200)]  # 食品类:奶酪、香蕉
]

max_p, selected = forced_group_knapsack(Wmax, groups)
print(f"最大收益:{max_p}")
print(f"选中物品(重量,收益):{selected}")

运行后会输出:

最大收益:205
选中物品(重量,收益):[(150, 5), (250, 200)]

完全匹配你示例中的最优解:《小王子》(150,5)+香蕉(250,200),总重量刚好400,总收益205。

额外提示

  • 如果某个类别中所有物品的重量都超过剩余背包容量,该状态会保持负无穷,代表无解,需要提前判断输入合法性。
  • 空间优化:可以把二维DP数组压缩成一维,从后往前遍历重量,避免覆盖未使用的状态,逻辑和普通分组背包类似,但要严格遵守「必须选当前组物品」的约束。

内容的提问来源于stack exchange,提问作者Cornélius Cellier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 15:49:06