NeFut Logo NeFut
EN 管理员登录

深度解析复杂背包问题:分组与混合背包的状态设计与算法推导

发布于:2026-05-27 09:17 最后更新:2026-06-10 09:24
#algorithm #Dynamic Programming #DP

核心逻辑与数学原理

复杂背包问题(如混合背包、分组背包)的本质是状态空间的降维映射与决策树的剪枝。在考场上面对复杂的限制条件,我们需要通过重新定义“阶段”与“状态内层互斥性”,将非线性的限制强行映射到线性的背包轴上。

1. 分组背包 (Grouped Knapsack) 的互斥逻辑

分组背包规定:物品被划分为若干组,每组内的物品相互互斥(即每组至多只能选一件)。

2. 混合背包 (Mixed Knapsack) 的多态融合

混合背包是指有的物品只能取一次(01),有的能取无限次(完全),有的能取有限次(多重)。


状态设计与算法推导

分组背包的数学推导与一维降维

$$f[j] = \max \bigg( f[j], \max_{(w, v) \text{ in } G_k} \bigg\{ f[j - w] + v \bigg\} \bigg)$$


C++ 标准源码(NOIP风格)

以下源码在一个程序中融合了分组背包混合背包的考场标准实现,完美适配 Linux g++ -O2 环境。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int MAXV = 1005;
int f[MAXV];

struct Item {
    int w, v;
};

// 存储分组背包的数据结构
vector<Item> group[105]; 

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, max_v;
    if (!(cin >> n >> max_v)) return 0;

    // ==================== 典型场景 1: 混合背包处理 ====================
    for (int i = 1; i <= n; ++i) {
        int w, v, p; // p 表示数量:p=-1 代表01背包,p=0 代表完全背包,p>0 代表多重背包
        cin >> w >> v >> p;

        if (p == -1) {
            // 01背包:直接一维倒序
            for (int j = max_v; j >= w; --j) {
                f[j] = max(f[j], f[j - w] + v);
            }
        } 
        else if (p == 0) {
            // 完全背包:直接一维正序
            for (int j = w; j <= max_v; ++j) {
                f[j] = max(f[j], f[j - w] + v);
            }
        } 
        else if (p > 0) {
            // 多重背包:考场直接二进制拆分为 01 背包颗粒,原地消化
            for (int k = 1; p >= k; k <<= 1) {
                int cur_w = k * w;
                int cur_v = k * v;
                for (int j = max_v; j >= cur_w; --j) {
                    f[j] = max(f[j], f[j - cur_w] + cur_v);
                }
                p -= k;
            }
            if (p > 0) {
                int cur_w = p * w;
                int cur_v = p * v;
                for (int j = max_v; j >= cur_w; --j) {
                    f[j] = max(f[j], f[j - cur_w] + cur_v);
                }
            }
        }
    }

    // ==================== 典型场景 2: 分组背包处理 ====================
    int m, g_num; // m 件物品,g_num 个组
    cin >> m >> g_num;
    for (int i = 1; i <= m; ++i) {
        int w, v, g_id;
        cin >> w >> v >> g_id;
        group[g_id].push_back({w, v});
    }

    for (int k = 1; k <= g_num; ++k) {
        // 致命踩坑点:容量循环必须夹在“组循环”与“组内物品循环”中间
        for (int j = max_v; j >= 0; --j) {
            for (const auto& item : group[k]) {
                if (j >= item.w) {
                    f[j] = max(f[j], f[j - item.w] + item.v);
                }
            }
        }
    }

    cout << f[max_v] << "\n";
    return 0;
}

NOIP 实战避坑指南

1. 分组背包错将物品循环写在容量循环之外

2. 混合背包多重背包拆分时未动态同步容量边界


经典 NOIP/洛谷 真题

1. 洛谷 P1757 通天之分组背包

2. 洛谷 P1064 [NOIP2006 提高组] 金明的预算方案

  1. 只买主件;
  2. 买主件 + 附件 1;
  3. 买主件 + 附件 2;
  4. 买主件 + 附件 1 + 附件 2。 将这 4 种组合的总体积与总价值分别计算出来,作为 4 个并列的物品存入该主件对应的组中。接下来直接套用标准分组背包的循环控制结构,即可将复杂的树状依赖限制完美降维,转化为线性空间上的互斥选择。
原文链接: local://14.4

[h] 返回首页