问题类型
核心区别
时间复杂度
典型应用场景
0-1背包
物品唯一
O(NV)
单次采购决策
完全背包
物品无限
O(NV)
货币组合问题
多重背包
物品数量限制
O(NVlogS)
资源有限分配
混合背包
物品数量不固定
二维费用背包
双重约束
O(NMK)
多维度资源优化
分组背包
组内互斥
O(NV)
品牌选择问题
有依赖的背包
主件附件依赖
O($$NV^2$$)
组合商品购买
求背包方案数
求背包具体方案数
0/1背包 二维 核心逻辑 :
二维数组存储状态,dp[i][j]
表示前i个物品在容量j时的最大价值
时间/空间复杂度:O(n*m)
特点:直观体现状态转移过程
#include <iostream> using namespace std;const int N = 1010 ;int v[N], w[N]; int dp[N][N]; int n, m; int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> v[i] >> w[i]; for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= m; j++) { dp[i][j] = dp[i-1 ][j]; if (j >= w[i]) dp[i][j] = max (dp[i][j], dp[i-1 ][j-w[i]] + v[i]); } cout << dp[n][m]; return 0 ; }
01背包:滚动数组优化版 优化亮点 :
空间复杂度降为O(m)
关键点:逆序更新避免重复选取物品
状态转移方程压缩到一维数组
#include <iostream> using namespace std;const int N = 1010 ;int v[N], w[N];int dp[N]; int main () { int n, m; cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> w[i] >> v[i]; for (int i = 1 ; i <= n; i++) for (int j = m; j >= w[i]; j--) dp[j] = max (dp[j], dp[j-w[i]] + v[i]); cout << dp[m]; return 0 ; }
倒序的缘故,那么接下来枚举的j都要比先前的j要来的小,当某个位置更新数据时,并不会调用其位置后面的数据,一定会是先调用其位置之前的旧数据, 然后再将当前位置的旧数据覆盖,也就保证了数据更新的有效性。
vector<int > weight = {1 , 3 , 4 }; vector<int > value = {15 , 20 , 30 }; int bag = 4 ;
完全背包 问题特性 :
输入样例:
3 5 1 2 // 物品1(体积1,价值2) 2 3 // 物品2(体积2,价值3) 3 4 // 物品3(体积3,价值4)
输出结果:
#include <iostream> #include <cstring> using namespace std;const int N = 1010 ; int v[N], w[N]; int dp[N]; int main () { int n, m; cin >> n >> m; for (int i = 1 ; i <= n; ++i) cin >> v[i] >> w[i]; memset (dp, 0 , sizeof dp); for (int i = 1 ; i <= n; ++i) for (int j = v[i]; j <= m; ++j) dp[j] = max (dp[j], dp[j - v[i]] + w[i]); cout << dp[m]; return 0 ; }
多重背包 核心改进 :
增加物品数量限制s[i]
选取次数k 受两个条件约束:k <= s[i]
和k*v[i] <= j
#include <iostream> using namespace std;const int N = 110 ;int v[N], w[N], s[N]; int dp[N][N];int main () { int n, m; cin >> n >> m; for (int i=1 ; i<=n; i++) cin >> v[i] >> w[i] >> s[i]; for (int i=1 ; i<=n; i++) for (int j=1 ; j<=m; j++) for (int k=0 ; k<=s[i] && k*v[i]<=j; k++) dp[i][j] = max (dp[i][j], dp[i-1 ][j-k*v[i]] + k*w[i]); cout << dp[n][m]; return 0 ; }
多重背包:二进制优化版 #include <iostream> #include <vector> using namespace std;struct Good { int v, w; };const int N = 25000 ; vector<Good> goods; int dp[N];int main () { int n, m; cin >> n >> m; for (int i=0 ; i<n; i++) { int v, w, s; cin >> v >> w >> s; for (int k=1 ; k<=s; k*=2 ) { s -= k; goods.push_back ({k*v, k*w}); } if (s>0 ) goods.push_back ({s*v, s*w}); } for (auto good : goods) for (int j=m; j>=good.v; j--) dp[j] = max (dp[j], dp[j-good.v] + good.w); cout << dp[m]; return 0 ; }
优化原理 :
将数量s分解为1+2+4+…+剩余值,组合可表示任意数量
时间复杂度从O(n*m*s)降为O(n*m*log s)
空间换时间的典型优化策略
混合背包 #include <iostream> #include <vector> using namespace std;struct Item { int weight; int value; }; vector<Item> convertItems (int m) { vector<Item> items; int n, type; cin >> n; while (n--) { int w, v, s; cin >> w >> v >> s; if (s == -1 ) { items.push_back ({w, v}); } else if (s == 0 ) { s = m / w; for (int k=1 ; k<=s; k*=2 ) { items.push_back ({k*w, k*v}); s -= k; } if (s > 0 ) items.push_back ({s*w, s*v}); } else { for (int k=1 ; k<=s; k*=2 ) { items.push_back ({k*w, k*v}); s -= k; } if (s > 0 ) items.push_back ({s*w, s*v}); } } return items; } int main () { int m; cin >> m; vector<Item> items = convertItems (m); vector<int > dp (m + 1 , 0 ) ; for (auto & item : items) { for (int j = m; j >= item.weight; j--) { if (dp[j] < dp[j - item.weight] + item.value) { dp[j] = dp[j - item.weight] + item.value; } } } cout << "最大价值: " << dp[m] << endl; return 0 ; }
二维费用背包 又叫双约束背包 #include <iostream> using namespace std;const int N = 1e3 + 10 , M1 = 1e2 + 10 , M2 = 1e2 + 10 ;int w[N], c[N]; int dp[M1][M2]; int main () { int n, m1, m2; cin >> m1 >> m2 >> n; for (int i=1 ; i<=n; i++) cin >> w[i] >> c[i]; memset (dp, 0x3f , sizeof dp); dp = 0 ; for (int i=1 ; i<=n; i++) for (int j=m1; j>=w[i]; j--) for (int k=m2; k>=c[i]; k--) dp[j][k] = min (dp[j][k], dp[j-w[i]][k-c[i]] + 1 ); cout << dp[m1][m2]; return 0 ; }
核心逻辑 :
双维度逆向遍历保证物品只选一次
dp[j][k]
表示满足容量j和k时的最小代价
适用场景:无人机送货(重量+体积双限制)
分组背包 实现特点 :
三层嵌套循环结构
外层:组别遍历
中层:容量逆向扫描
内层:组内物品决策
关键限制:每组最多选1个物品
典型应用:电商平台同品牌商品选择
#include <iostream> #include <vector> using namespace std;const int N=1e4 +10 , M=2e2 +10 , T=1e2 +10 ;int w[N],c[N]; vector<int > group[N]; int dp[M]; int main () { int m,n,t; cin>>m>>n>>t; for (int i=1 ;i<=n;i++){ int p; cin>>w[i]>>c[i]>>p; group[p].push_back (i); } for (int g=1 ;g<=t;g++) for (int j=m;j>=0 ;j--) for (int k:group[g]) if (j>=w[k]) dp[j]=max (dp[j], dp[j-w[k]]+c[k]); cout<<dp[m]; return 0 ; }
有依赖背包(主附件) 问题描述 在有依赖背包问题中,物品被分为主件和附件两种类型。每个附件只能在其对应的主件被选择的情况下才能被选择。目标是在背包容量的限制下,选择合适的物品(主件及其附件的组合),使得总价值最大化。
假设背包容量为 V
,总共有 n
件物品,每件物品要么是主件,要么是附件,每件物品有对应的重量 w[i]
和价值 v[i]
。
输入输出示例 输入示例 :
10 5 8 4 0 2 1 1 3 1 1 4 2 0 5 3 4
第一行:背包容量 V = 10
,物品数量 n = 5
。
接下来的 n
行:每行包含三个整数 v[i]
(价值)、w[i]
(重量)、p[i]
(依赖关系)。如果 p[i] = 0
,表示该物品是主件;否则,表示该物品依赖于编号为 p[i]
的主件。
输出示例 :
输出一个整数,表示在给定背包容量下能获得的最大价值。
C++ 代码实现 Cpp#include <iostream> #include <cstring> using namespace std;const int N = 610 ; const int M = 1010 ; struct Item { int v, w; }; Item master[N], accessory1[N], accessory2[N]; int n, m; int dp[M];int main () { cin >> m >> n; int cnt = 0 ; for (int i = 1 ; i <= n; i++) { int v, w, p; cin >> v >> w >> p; if (p == 0 ) { master[++cnt] = {v, w}; } else { if (!accessory1[p].v) { accessory1[p] = {v, w}; } else { accessory2[p] = {v, w}; } } } memset (dp, 0 , sizeof dp); for (int i = 1 ; i <= cnt; i++) { for (int j = m; j >= master[i].w; j--) { int val = dp[j - master[i].w] + master[i].v; dp[j] = max (dp[j], val); if (j >= master[i].w + accessory1[i].w) { val = dp[j - master[i].w - accessory1[i].w] + master[i].v + accessory1[i].v; dp[j] = max (dp[j], val); } if (j >= master[i].w + accessory2[i].w) { val = dp[j - master[i].w - accessory2[i].w] + master[i].v + accessory2[i].v; dp[j] = max (dp[j], val); } if (j >= master[i].w + accessory1[i].w + accessory2[i].w) { val = dp[j - master[i].w - accessory1[i].w - accessory2[i].w] + master[i].v + accessory1[i].v + accessory2[i].v; dp[j] = max (dp[j], val); } } } cout << dp[m] << endl; return 0 ; }
四重组合决策:
单独主件
主件+附件A
主件+附件B
主件+双附件
实际应用:电脑配置(主板+可选配件)
求背包问题的方案数 求解用给定物品装满背包的不同组合方式总数。
dp[j] = 不选该物品的方案数 + 选该物品的方案数
#include <iostream> #include <cstring> using namespace std;const int N = 1010 , M = 1010 ;int w[N]; int dp[M]; int main () { int n, m; cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> w[i]; memset (dp, 0 , sizeof (dp)); dp[0 ] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = w[i]; j <= m; j++) { dp[j] += dp[j - w[i]]; } } cout << dp[m] << endl; return 0 ; }
背包的具体方案数 01背包的具体方案数 不仅能计算最大价值,还能回溯输出具体选择的物品方案。
vector<int > res; for (int i = n, j = m; i >= 1 ; i--) { if (j >= w[i] && dp[i][j] == dp[i-1 ][j-w[i]] + c[i]) { res.push_back (i); j -= w[i]; } }
示例:
输入: 5 3 // 背包容量5,3个物品 2 3 // 物品1:重量2,价值3 3 4 // 物品2:重量3,价值4 4 5 // 物品3:重量4,价值5 输出: 2 1 // 选择物品2和1,总重量5,总价值7
解释 :
最大价值为7(物品2价值4 + 物品1价值3)。
回溯时会优先检测到物品2被选中
完全背包的具体方案
特点 :每物品可选无限次。
调整思路
回溯步骤
for (int i = 1 ; i <= n; ++i) { for (int j = w[i]; j <= m; ++j) { if (dp[j - w[i]] + v[i] > dp[j]) { dp[j] = dp[j - w[i]] + v[i]; cnt[i][j] = cnt[i][j - w[i]] + 1 ; } } } vector<int > res; for (int i = n, j = m; i > 0 ; --i) { while (cnt[i][j] > 0 ) { res.push_back (i); j -= w[i]; cnt[i][j]--; } } for (int item : res) cout << item << " " ; cout << endl; return 0 ; }
要点 :需循环检查同一物品被选取的次数。