C++ 背包九讲:从入门到精通的动态规划专题

1. 引言

背包问题是动态规划领域最经典、最基础的一类问题,也是算法竞赛和面试中高频出现的考点。所谓「背包九讲」,是指围绕背包问题展开的九种经典变体与解法思路,从最简单的 01 背包,到完全背包、多重背包,再到分组背包、依赖背包等进阶模型。掌握这些模型,不仅能解决背包本身,更能帮助你建立动态规划的状态设计思维。

本文以 C++ 为语言载体,逐讲拆解九种背包问题的定义、状态转移方程、核心代码与优化技巧,并给出可直接运行的示例。

2. 第一讲:01 背包

01 背包是最基础的背包模型:有 N 件物品和一个容量为 V 的背包,每件物品只能选择放入一次(0 或 1 次),第 i 件物品的体积为 ci,价值为 wi,求在不超过背包容量的前提下能获得的最大总价值。

状态定义:dpj 表示容量为 j 的背包能装下的最大价值。核心转移方程为:

cpp 复制代码
for (int i = 1; i <= N; i++) {
    for (int j = V; j >= c[i]; j--) {
        dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    }
}

这里内层循环必须从大到小遍历,目的是保证每件物品只被使用一次,避免重复选取。

3. 第二讲:完全背包

完全背包与 01 背包的唯一区别在于:每件物品可以无限次选取。因此内层循环改为从小到大遍历,即可实现同一物品的多次累加。

cpp 复制代码
for (int i = 1; i <= N; i++) {
    for (int j = c[i]; j <= V; j++) {
        dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    }
}

两种背包代码只差一个循环方向,但语义完全不同,务必理解其背后的递推逻辑。

4. 第三讲:多重背包

多重背包中,第 i 件物品最多可选 ki 次。朴素做法是在 01 背包基础上再枚举选取数量,时间复杂度为 O(N·V·k)。更高效的做法是二进制拆分:把 ki 件物品拆成若干组,每组数量为 1、2、4、......、剩余部分,从而转化为 01 背包求解。

cpp 复制代码
for (int i = 1; i <= N; i++) {
    int num = k[i];
    for (int p = 1; p <= num; p <<= 1) {
        int cnt = min(p, num);
        for (int j = V; j >= cnt * c[i]; j--) {
            dp[j] = max(dp[j], dp[j - cnt * c[i]] + cnt * w[i]);
        }
        num -= cnt;
    }
}

5. 第四讲:混合背包

混合背包是指同一问题中同时存在 01 背包、完全背包和多重背包的物品。处理方式很直接:遍历每件物品时,根据其类型选择对应的循环方向与拆分策略即可。

cpp 复制代码
for (int i = 1; i <= N; i++) {
    if (type[i] == 0) { // 01 背包
        for (int j = V; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    } else if (type[i] == 1) { // 完全背包
        for (int j = c[i]; j <= V; j++)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    } else { // 多重背包,二进制拆分后按 01 处理
        // 省略拆分细节
    }
}

6. 第五讲:二维费用背包

二维费用背包是指每件物品同时占用两种资源(如体积和重量),背包也有两个容量上限。此时状态扩展为二维:dpjk 表示体积不超过 j、重量不超过 k 时的最大价值。

cpp 复制代码
for (int i = 1; i <= N; i++) {
    for (int j = V; j >= c[i]; j--) {
        for (int k = M; k >= d[i]; k--) {
            dp[j][k] = max(dp[j][k], dp[j - c[i]][k - d[i]] + w[i]);
        }
    }
}

7. 第六讲:分组背包

分组背包中,物品被划分为若干组,每组内最多只能选择一件。处理时先枚举组,再枚举容量,最后枚举组内物品,保证每组至多取一个。

cpp 复制代码
for (int g = 1; g <= G; g++) {
    for (int j = V; j >= 0; j--) {
        for (int k = 0; k < group[g].size(); k++) {
            int c = group[g][k].c, w = group[g][k].w;
            if (j >= c) dp[j] = max(dp[j], dp[j - c] + w);
        }
    }
}

8. 第七讲:有依赖的背包

有依赖的背包是指某些物品必须在其「主件」被选取后才能选取,典型场景是树形结构。一般思路是:对每个主件,先对其附件做一次 01 背包,得到该主件在不同容量下的最优价值,再与主件合并,最后对所有主件做一次分组背包。

9. 第八讲:泛化物品与背包问题扩展

泛化物品是指价值随分配容量变化的物品,即 w = f(c)。这类问题可以统一用「泛化物品」的视角看待:每种背包模型本质上都是在容量与价值之间做最优分配。理解这一点,有助于把新问题抽象为已知模型。

10. 第九讲:背包问题方案总数与最优方案

除了求最大价值,背包问题还常要求统计方案总数或输出具体方案。方案总数只需把 max 改为求和:

cpp 复制代码
dp[0] = 1;
for (int i = 1; i <= N; i++) {
    for (int j = V; j >= c[i]; j--) {
        dp[j] += dp[j - c[i]];
    }
}

输出具体方案则需要在转移时记录选择路径,通常用二维数组 pathij 标记第 i 件物品在容量 j 下是否被选取,最后逆序回溯。

11. 总结与练习建议

背包九讲的核心在于理解「状态定义」与「循环顺序」背后的递推语义。建议按以下顺序练习:先吃透 01 背包与完全背包的代码差异,再掌握多重背包的二进制拆分,最后挑战分组与依赖背包。每道题都亲手写出状态转移方程,再对照代码验证,才能真正内化动态规划思想。

12. 10 道练习题(洛谷题号)

下面给出 10 道覆盖背包九讲各知识点的洛谷练习题,建议按难度循序渐进地完成:

  1. P1048 采药:01 背包入门题,适合用来熟悉状态转移方程与逆序循环。
  2. P1616 疯狂的采药:完全背包基础题,体会正序循环与 01 背包的差异。
  3. P1060 开心的金明:01 背包 + 价值加权,练习把题目条件转化为背包模型。
  4. P1049 装箱问题:01 背包变形,求剩余空间最小,训练逆向思维。
  5. P1164 小A点菜:方案总数类背包,对应第九讲内容,练习把 max 改为求和。
  6. P1776 宝物筛选:多重背包经典题,必须使用二进制拆分优化才能通过。
  7. P1833 樱花:混合背包综合题,同时包含 01、完全与多重背包,对应第四讲。
  8. P1855 榨取kkksc03:二维费用背包,对应第五讲,同时限制时间和金钱两种资源。
  9. P1757 通天之分组背包:分组背包模板题,对应第六讲,注意枚举顺序。
  10. P1064 金明的预算方案:有依赖的背包经典题,对应第七讲,主件与附件需合并处理。
相关推荐
xys_6784 小时前
JavaEE通关手册:初识Java
java·开发语言
努力努力再努力wz4 小时前
【Lua 进阶系列】从 C++ 对象到 Lua 变量:一文理解 userdata、Lua 栈与元表绑定机制
c++·junit·lua
慧都产品智能化5 小时前
Qt 表格升级攻略:复用已有编辑器,减少重复开发
c++·qt·编辑器·qt6.3
ShineWinsu5 小时前
对于Qt:QTableWidget常见属性以及QTableWidgetItem类的解析
开发语言·c++·qt·gui·客户端·表格·qtablewidget
ShineWinsu6 小时前
对于Qt:QListWidget常见属性以及QListWidgetItem类的解析
开发语言·c++·qt·客户端·列表·qlistwidget·qlistwidgetitem
坤坤子吖7 小时前
Linux网络编程:自定义应用层协议与 JSON 序列化
linux·服务器·网络·c++·笔记·json
吴名氏.7 小时前
Java转AI Agent教程(第十二周)检索与切分
java·开发语言·人工智能·ai·ai agent
瑞雪兆丰年兮7 小时前
JavaWeb-day10-SpringBootWeb案例
java·开发语言·javaweb·web
Elastic 中国社区官方博客8 小时前
更少的位数,相同的召回率:DiskBBQ 更保守的自动校准扫描
大数据·运维·开发语言·人工智能·elasticsearch·搜索引擎
三雷科技8 小时前
第6章第4节:自定义分词器
开发语言·c#