NeFut Logo NeFut
EN 管理员登录

高效算法解析:背包问题的多维优化与实现

发布于:2026-05-27 09:17 最后更新:2026-06-06 13:04
#C++ #Tutorial

核心逻辑与数学原理

背包问题是一类典型的高维约束状态组合优化问题,其核心在于:在有限资源边界(背包容量 $V$)下,如何通过对离散决策(选或不选、选多少个)的阶段性扫描,使目标函数(总价值)达到极值。

三大核心背包模型的数学本质各异:

  1. 01 背包 (01 Knapsack):每种物品仅一件。决策具有二重性,状态转移依赖于上一阶段未受当前物品污染的纯净历史。
  2. 完全背包 (Unbounded Knapsack):每种物品无限件。决策具有连续性,状态转移依赖于当前阶段已加入当前物品的最新状态。
  3. 多重背包 (Bounded Knapsack):每种物品有固定件数限制 $C_i$。直接暴力拆分会导致复杂度退化为 $O(V \sum C_i)$。

$$f[r + q \cdot w_i] = \max_{q - C_i \le k \le q} \{ f[r + k \cdot w_i] - k \cdot v_i \} + q \cdot v_i$$

这是一个标准的滑动窗口最值问题,通过单调队列维护 $f[r + k \cdot w_i] - k \cdot v_i$ 的单调递减序列,可将状态转移耗时优化至 $O(1)$,全局复杂度降至极限的 $O(N \cdot V)$。


状态设计与算法推导

使用一维滚动数组压缩空间后,各类背包的状态空间、转移方程与循环序有着严密的几何逻辑:

1. 01 背包

2. 完全背包

3. 多重背包二进制拆分


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

以下源码在同一程序中给出 01 背包、完全背包与多重背包(二进制拆分)的一维高效工程实现。

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

using namespace std;

const int MAXV = 20005; // 针对背容量
int f[MAXV];

// 用于多重背包拆分出来的扁平化物品结构
struct Item {
    int w, v;
};

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

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

    vector<Item> items;

    for (int i = 1; i <= n; ++i) {
        int w, v, c, type;
        cin >> w >> v >> c >> type; // type: 1->01, 2->完全, 3->多重

        if (type == 1) {
            items.push_back({w, v});
        } 
        else if (type == 2) {
            // 完全背包在后面单独用正序处理,此处由于需要统一调度,可暂不放入01队列
            // 考场为了代码高内聚,通常直接把三种背包合在主循环里拆分处理
        } 
        else if (type == 3) {
            // 多重背包:二进制拆分核心逻辑
            for (int k = 1; c >= k; k <<= 1) {
                items.push_back({k * w, k * v});
                c -= k;
            }
            if (c > 0) {
                items.push_back({c * w, c * v});
            }
        }
    }

    // ==================== 01背包 & 多重背包(拆分后) 的统一倒序驱动 ====================
    for (const auto& item : items) {
        for (int j = max_v; j >= item.w; --j) { // 致命踩坑点:必须严格逆序,否则退化为完全背包
            f[j] = max(f[j], f[j - item.w] + item.v);
        }
    }

    // ==================== 完全背包的独立正序驱动(假设单独读入了一组完全背包物品) ====================
    // 此处仅做示范:若物品是完全背包类型,循环必须改写为正序
    int comp_w = 5, comp_v = 10; // 模拟一个完全背包物品
    for (int j = comp_w; j <= max_v; ++j) { // 正序驱动,允许状态自我污染,承接多件
        f[j] = max(f[j], f[j - comp_w] + comp_v);
    }

    cout << f[max_v] << "\n";

    return 0;
}

NOIP 实战避坑指南

1. 01 背包一维压缩时容量循环方向写反

2. 多重背包二进制拆分时漏掉剩余尾数


经典 NOIP/洛谷 真题

1. 洛谷 P1776 宝物筛选

2. 洛谷 P1616 疯狂的采药

原文链接: local://14.3

[h] 返回首页