
在动态规划的背包世界里,01 背包和完全背包是入门的 “敲门砖”,但真正的算法实战中,问题往往不会这么 “纯粹”—— 物品可能有使用次数限制、可能分成互斥的组、可能同时存在多种选择规则,甚至会有多重资源约束。这些复杂场景,正是多重背包、分组背包、混合背包和多维费用背包要解决的核心问题。 如果你已经掌握了基础背包的逻辑,恭喜你!接下来这篇文章,将带你 “升级打怪”,攻克背包问题的四大扩展模型。我们会从实际场景出发,拆解每种模型的核心痛点,推导状态转移方程,提供完整的 C++ 代码实现,还会分享优化技巧和避坑指南。 全文干货密集,适合有基础背包功底、想进阶提升的算法爱好者。建议收藏后慢慢研读,跟着代码敲一遍,彻底吃透背包扩展的精髓~下面就我们开始吧!
在开始具体模型之前,我们先统一一个核心认知:所有背包扩展模型,本质都是基础背包的 “规则变种”。
它们的核心思想始终没变:
dp[j](或多维dp)存储 “使用不超过j资源时的最大价值”(或其他目标);变化的只是 “选择规则” 和 “约束条件”:
掌握这个 “不变应万变” 的逻辑,再学习扩展模型就会事半功倍。接下来,我们逐个拆解每种模型。
多重背包的核心约束是:每种物品有固定的使用次数上限(既不能像 01 背包只能选 1 次,也不能像完全背包选无限次)。
举个生活化的例子:你去超市采购,背包容量 5L。货架上有 2 种物品:
请问如何选择,才能让背包内物品总价值最大?
这就是典型的多重背包问题 —— 每个物品的选择次数被限制,需要在次数和容量双重约束下做最优决策。
模型 | 选择次数约束 | 核心差异点 |
|---|---|---|
01 背包 | 最多 1 次 | 容量枚举从右到左,避免重复选择 |
完全背包 | 无限次 | 容量枚举从左到右,允许重复选择 |
多重背包 | 最多x[i]次 | 需额外枚举每个物品的使用次数 |
多重背包的暴力解法,本质是 “将多重背包转化为 01 背包”—— 把每个有x[i]次使用限制的物品,拆成x[i]个完全相同的 01 背包物品,然后用 01 背包的解法求解。
比如物品 1 有 4 件,就拆成 4 个 “体积 2L、价值 10” 的独立物品,再按 01 背包的 “右到左” 枚举容量即可。
dp[j]:使用不超过j的容量,能获得的最大价值。
对于每个物品i,枚举其使用次数k(0 ≤ k ≤ x[i]且k*v[i] ≤ j):
dp[j] = max(dp[j], dp[j - k*v[i]] + k*w[i])k=0:不选该物品,价值不变;k>0:选k件该物品,容量消耗k*v[i],价值增加k*w[i]。#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110; // 物品数、容量最大值
int n, T; // n:物品种数,T:背包容量
int x[N], w[N], v[N]; // x:使用次数上限,w:价值,v:体积
int dp[N]; // dp[j]:容量j时的最大价值
int main() {
// 示例输入:2种物品,容量8
n = 2, T = 8;
x[1] = 4, v[1] = 2, w[1] = 100; // 物品1:4件,体积2,价值100
x[2] = 2, v[2] = 4, w[2] = 100; // 物品2:2件,体积4,价值100
memset(dp, 0, sizeof dp);
// 多重背包暴力解法:枚举物品、容量、使用次数
for (int i = 1; i <= n; i++) {
// 01背包逻辑:容量从右到左
for (int j = T; j >= 0; j--) {
// 枚举使用次数k:0到x[i],且k*v[i] ≤ j
for (int k = 1; k <= x[i] && k * v[i] <= j; k++) {
dp[j] = max(dp[j], dp[j - k * v[i]] + k * w[i]);
}
}
}
cout << "多重背包暴力版最大价值:" << dp[T] << endl; // 输出400
return 0;
}O(n*T*max_x),其中max_x是物品的最大使用次数;O(T)。 优点:逻辑简单,容易理解,适合n、T、max_x都较小的场景(如n≤100、T≤100、max_x≤20)。
缺点:效率较低,当max_x较大(如1e3)时,会超时。
暴力解法的问题在于 “重复枚举相同物品”,而二进制优化的核心是:用二进制数拆分使用次数x[i],将x[i]个相同物品转化为log2(x[i])个 “超级物品”,从而将时间复杂度从O(n*T*max_x)降到O(n*T*log(max_x))。
二进制拆分的原理:
x都可以拆成2^0, 2^1, 2^2, ..., 2^k, r的形式(其中r < 2^(k+1));[0, x]区间内的所有整数。 比如x=9,可以拆成1(2^0)、2(2^1)、4(2^2)、2(r=9-7=2),这四个数能组合出 0-9 之间的所有整数(如 3=1+2,5=1+4,9=1+2+4+2)。
i,使用次数x[i];t=1(二进制基数);x[i] >= t时,拆出一个 “超级物品”:体积t*v[i],价值t*w[i],x[i] -= t,t *= 2;x[i] > 0,拆出最后一个 “超级物品”:体积x[i]*v[i],价值x[i]*w[i];#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110 * 5; // 拆分后最大物品数:110种物品,每种最多拆log2(20)=5次
const int M = 110; // 背包容量最大值
int n, T;
int w[N], v[N], pos; // pos:拆分后超级物品的下标
int dp[M];
int main() {
n = 2, T = 8;
// 原始物品:x[1]=4, v=2, w=100;x[2]=2, v=4, w=100
int x[] = {0, 4, 2};
int v_ori[] = {0, 2, 4};
int w_ori[] = {0, 100, 100};
// 二进制拆分
for (int i = 1; i <= n; i++) {
int cnt = x[i]; // 剩余使用次数
int t = 1; // 二进制基数
while (cnt >= t) {
pos++;
v[pos] = t * v_ori[i];
w[pos] = t * w_ori[i];
cnt -= t;
t *= 2;
}
// 处理剩余部分
if (cnt > 0) {
pos++;
v[pos] = cnt * v_ori[i];
w[pos] = cnt * w_ori[i];
}
}
// 01背包求解拆分后的超级物品
memset(dp, 0, sizeof dp);
for (int i = 1; i <= pos; i++) {
for (int j = T; j >= v[i]; j--) {
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << "多重背包二进制优化版最大价值:" << dp[T] << endl; // 输出400
return 0;
} 以x[i]=1e3为例:
log2(1e3)≈10,只需循环 10 次。效率提升非常显著,是多重背包的主流优化方法。
题目链接:https://www.luogu.com.cn/problem/P1077

小明的花店门口要摆m盆花,有n种花,第i种花最多摆a[i]盆。同一种花要放在一起,不同种花按标号顺序摆放。求有多少种不同的摆花方案(答案对1e6+7取模)。
这是多重背包的 “方案数” 变种:
n种花;m盆花;i种花最多选a[i]盆;m盆的方案数。 dp[j]:恰好摆j盆花的方案数。
dp[j] = (dp[j] + dp[j - k]) % MODk:第i种花选k盆(1 ≤ k ≤ min(a[i], j));dp[0] = 1(摆 0 盆花有 1 种方案:啥也不摆)。#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
const int MOD = 1e6 + 7;
int n, m;
int a[N];
int dp[N]; // dp[j]:恰好摆j盆的方案数
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
memset(dp, 0, sizeof dp);
dp[0] = 1; // 初始状态
for (int i = 1; i <= n; i++) {
// 多重背包方案数:容量从右到左(避免重复计数)
for (int j = m; j >= 0; j--) {
// 枚举选k盆第i种花
for (int k = 1; k <= a[i] && k <= j; k++) {
dp[j] = (dp[j] + dp[j - k]) % MOD;
}
}
}
cout << dp[m] << endl;
return 0;
}2 4
3 22分组背包的核心约束是:物品被分成若干组,每组中的物品相互互斥,最多选择一个。
生活化场景:你要参加旅行,背包容量 5L。物品分成 3 组:
每组最多选一个物品,如何选择能让总价值最大?
这就是分组背包的核心场景 —— 每组内部是 “选或不选”,但只能选一个,组与组之间是 “独立选择”。
分组背包的解法可以概括为 “组内 01 背包,组间顺序枚举”:
dp[j]:使用不超过j的容量,选择若干组物品(每组最多一个)的最大价值。
对于第i组的第k个物品(体积v,价值w):
dp[j] = max(dp[j], dp[j - v] + w)j >= v;#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
typedef pair<int, int> PII; // 存储物品的体积和价值
const int N = 1010; // 容量最大值
int m, n; // m:背包容量,n:物品总数
vector<PII> groups[N]; // groups[i]:第i组的所有物品
int dp[N];
int main() {
// 示例输入:容量45,3个物品,分2组
m = 45, n = 3;
groups[1].push_back({10, 10}); // 组1:体积10,价值10
groups[1].push_back({10, 5}); // 组1:体积10,价值5
groups[2].push_back({50, 400});// 组2:体积50,价值400(超容量,无法选)
memset(dp, 0, sizeof dp);
// 分组背包:枚举每组
for (int i = 1; i <= 2; i++) {
// 组内01背包:容量从右到左
for (int j = m; j >= 0; j--) {
// 枚举组内每个物品
for (auto& item : groups[i]) {
int v = item.first, w = item.second;
if (j >= v) {
dp[j] = max(dp[j], dp[j - v] + w);
}
}
}
}
cout << "分组背包最大价值:" << dp[m] << endl; // 输出10
return 0;
}题目链接:https://www.luogu.com.cn/problem/P1757

有n件物品,总重量m。每件物品有重量a[i]、价值b[i]、所属组c[i]。每组内的物品相互冲突,最多选一个。求最大利用价值。
标准分组背包问题,只需将物品按组归类,然后按分组背包模板求解。
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int m, n; // m:总重量,n:物品数
vector<PII> g[N]; // g[i]:第i组的物品(重量,价值)
int dp[N];
int max_group; // 最大组号
int main() {
cin >> m >> n;
for (int i = 1; i <= n; i++) {
int a, b, c;
cin >> a >> b >> c;
g[c].push_back({a, b});
max_group = max(max_group, c);
}
memset(dp, 0, sizeof dp);
// 枚举每组
for (int i = 1; i <= max_group; i++) {
// 组内01背包:右到左枚举重量
for (int j = m; j >= 0; j--) {
for (auto& item : g[i]) {
int w = item.first, val = item.second;
if (j >= w) {
dp[j] = max(dp[j], dp[j - w] + val);
}
}
}
}
cout << dp[m] << endl;
return 0;
}45 3
10 10 1
10 5 1
50 400 210混合背包的核心特点是:同一问题中,同时存在 01 背包、完全背包、多重背包的物品。
生活化场景:你去商场购物,背包容量 10L:
如何选择能让总价值最大?
混合背包的解法,本质是 “分类处理”—— 对不同类型的物品,采用对应的背包求解逻辑。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010; // 容量最大值
int V; // 背包容量
int dp[N];
// 处理01背包物品
void zero_one_pack(int v, int w) {
for (int j = V; j >= v; j--) {
dp[j] = max(dp[j], dp[j - v] + w);
}
}
// 处理完全背包物品
void complete_pack(int v, int w) {
for (int j = v; j <= V; j++) {
dp[j] = max(dp[j], dp[j - v] + w);
}
}
// 处理多重背包物品(二进制优化)
void multiple_pack(int v, int w, int x) {
int t = 1;
while (x >= t) {
zero_one_pack(t * v, t * w);
x -= t;
t *= 2;
}
if (x > 0) {
zero_one_pack(x * v, x * w);
}
}
int main() {
V = 10; // 背包容量10
memset(dp, 0, sizeof dp);
// 01背包物品:衣服(v=3, w=20, x=1)
zero_one_pack(3, 20);
// 完全背包物品:矿泉水(v=1, w=2)
complete_pack(1, 2);
// 多重背包物品:纸巾(v=2, w=5, x=3)
multiple_pack(2, 5, 3);
cout << "混合背包最大价值:" << dp[V] << endl; // 输出39
return 0;
}题目链接:https://www.luogu.com.cn/problem/P1833

爱与愁大神有n棵樱花树,每棵树有观赏时间T[i]、美学值C[i]、观赏次数限制P[i](P[i]=0表示无限次,P[i]>0表示最多P[i]次)。他从T_s到T_e有m分钟时间,求最大美学值。
标准混合背包问题:
P[i]=0:完全背包;P[i]=1:01 背包;P[i]>1:多重背包。#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e4 + 10;
const int M = 1010; // 时间最大值(≤1000)
int n, m; // n:樱花树数,m:总时间
int t[N], c[N], p[N];
int dp[M];
void zero_one_pack(int v, int w) {
for (int j = m; j >= v; j--) {
dp[j] = max(dp[j], dp[j - v] + w);
}
}
void complete_pack(int v, int w) {
for (int j = v; j <= m; j++) {
dp[j] = max(dp[j], dp[j - v] + w);
}
}
void multiple_pack(int v, int w, int x) {
int cnt = x;
int k = 1;
while (cnt >= k) {
zero_one_pack(k * v, k * w);
cnt -= k;
k *= 2;
}
if (cnt > 0) {
zero_one_pack(cnt * v, cnt * w);
}
}
int main() {
// 读取时间:T_s和T_e转换为分钟差
int h1, m1, h2, m2;
char ch;
cin >> h1 >> ch >> m1 >> h2 >> ch >> m2 >> n;
m = h2 * 60 + m2 - (h1 * 60 + m1);
for (int i = 1; i <= n; i++) {
cin >> t[i] >> c[i] >> p[i];
}
memset(dp, 0, sizeof dp);
for (int i = 1; i <= n; i++) {
if (p[i] == 0) {
// 完全背包
complete_pack(t[i], c[i]);
} else if (p[i] == 1) {
// 01背包
zero_one_pack(t[i], c[i]);
} else {
// 多重背包
multiple_pack(t[i], c[i], p[i]);
}
}
cout << dp[m] << endl;
return 0;
}6:50 7:00 3
2 1 0
3 3 1
4 5 411多维费用背包的核心约束是:背包的约束条件不止一个(如体积 + 重量、时间 + 金钱、人数 + 空间等)。
生活化场景:你要组织一次露营,需要选择物品:
如何选择能让总价值最大?
多维费用背包的解法,本质是 “状态维度扩展”—— 将一维dp[j]扩展为多维dp[j1][j2]...,每个维度对应一个约束条件。
以二维费用背包(体积V+ 重量W)为例:
状态表示:dp[j][k]:使用不超过j的体积和k的重量,能获得的最大价值;
状态转移:对于物品(v,w,val),按 01 背包的 “逆序枚举” 更新:
dp[j][k] = max(dp[j][k], dp[j - v][k - w] + val)扩展:三维及以上费用背包,同理扩展状态维度和枚举维度。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int V = 110; // 体积最大值
const int W = 110; // 重量最大值
int dp[V][W]; // dp[j][k]:体积j,重量k的最大价值
int main() {
// 示例输入:体积约束10,重量约束15,3个物品
int max_v = 10, max_w = 15;
int items[3][3] = {
{5, 8, 20}, // 帐篷:体积5,重量8,价值20
{3, 5, 15}, // 睡袋:体积3,重量5,价值15
{2, 3, 10} // 背包:体积2,重量3,价值10
};
memset(dp, 0, sizeof dp);
// 二维费用背包:01背包逻辑,逆序枚举体积和重量
for (int i = 0; i < 3; i++) {
int v = items[i][0], w = items[i][1], val = items[i][2];
// 体积逆序
for (int j = max_v; j >= v; j--) {
// 重量逆序
for (int k = max_w; k >= w; k--) {
dp[j][k] = max(dp[j][k], dp[j - v][k - w] + val);
}
}
}
cout << "二维费用背包最大价值:" << dp[max_v][max_w] << endl; // 输出45
return 0;
}题目链接:https://www.luogu.com.cn/problem/P1910

有N个人选,每个人有资料价值A[i]、伪装能力B[i](越小越差)、工资C[i]。敌国探查能力M(总伪装能力≤M),手头有X元(总工资≤X)。求能拿到的最大资料价值。
二维费用背包:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int M = 1010; // 伪装能力最大值
const int X = 1010; // 工资最大值
int dp[M][X]; // dp[j][k]:伪装j,工资k的最大资料价值
int main() {
int N, max_B, max_C;
cin >> N >> max_B >> max_C;
int A[N+1], B[N+1], C[N+1];
for (int i = 1; i <= N; i++) {
cin >> A[i] >> B[i] >> C[i];
}
memset(dp, 0, sizeof dp);
// 二维费用01背包
for (int i = 1; i <= N; i++) {
int a = A[i], b = B[i], c = C[i];
// 伪装能力逆序
for (int j = max_B; j >= b; j--) {
// 工资逆序
for (int k = max_C; k >= c; k--) {
dp[j][k] = max(dp[j][k], dp[j - b][k - c] + a);
}
}
}
cout << dp[max_B][max_C] << endl;
return 0;
}3 10 12
10 1 11
1 9 1
7 10 1211模型 | 核心约束 | 状态表示 | 核心操作 | 时间复杂度 |
|---|---|---|---|---|
多重背包 | 物品最多选x[i]次 | 一维dp[j] | 二进制拆分 / 暴力枚举次数 | O(n*T*log(max_x)) |
分组背包 | 每组最多选一个物品 | 一维dp[j] | 组内 01 背包(右到左枚举) | O(n*T) |
混合背包 | 同时存在 01 / 完全 / 多重物品 | 一维dp[j] | 分类处理,按对应模型求解 | 取决于各模型占比 |
多维费用背包 | 多重资源约束(如体积 + 重量) | 多维dp[j1][j2] | 逆序枚举每个约束维度 | O(n*T1*T2*...*Tk) |
long long避免溢出。背包问题的扩展模型,本质是基础背包逻辑的 “组合” 与 “扩展”。无论是多重背包的次数约束、分组背包的互斥约束、混合背包的规则混杂,还是多维背包的多重约束,核心都离不开 “状态表示” 和 “状态转移” 这两个动态规划的灵魂。 学习这些模型的关键,不是死记硬背代码,而是理解每种约束对应的 “选择规则”,并将其转化为对应的背包逻辑。比如,“次数有限” 对应多重背包的二进制拆分,“互斥选择” 对应分组背包的组内 01 处理,“多重约束” 对应多维状态的扩展。 当你能灵活切换不同背包模型的解法,甚至能应对多种约束叠加的复杂场景时,就真正掌握了背包问题的核心。接下来,不妨尝试做一些综合类的背包题目,将这些模型融会贯通。 如果本文对你有帮助,别忘了点赞、收藏、转发三连~ 有任何疑问或建议,欢迎在评论区留言讨论!