C++背包问题

1. 概述:何为背包

背包问题 = 一些物品 (体积, 价值, 个数)+ 一个容量有限的背包 + 求最大价值。

1.1 01背包

01背包表示物品只能选0或1个,所以叫01背包。它是所有背包的基础。

1.1.1 普通版

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 30 + 10, M = 200 + 10;

/*
递推关系式 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + c[i])
递推初值  dp[0][0] = 0

答案就是 dp[n][m]
*/

int m, n;
LL w[N], c[N];
LL dp[N][M];

int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> c[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] >= 0) {
				dp[i][j] = max(dp[i][j], dp[i - 1][j - w[i]] + c[i]);
			}
		}
	}

	cout << dp[n][m] << "\n";

	return 0;
}

dp[i][j]表示当背包容量为j、选取前i件物品时背包中的最大价值。

1.1.2 优化版

由于dp数组占用空间为eq?N%5E2,N若是开到1e5就爆了,所以可以使用滚动数组优化。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 30 + 10, M = 200 + 10;



int m, n;
LL w[N], c[N];
LL dp[M];

int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> c[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]] + c[i]);
		}
	}
	cout << dp[m] << "\n";

	return 0;
}

 

优化后dp数组直接去掉了一维,空间优化了不少,不过注意j要倒着循环。

1.2 完全背包

完全背包表示物品可以选无限个(其实并不是无限个,而是w%5Bi%5D个)。

1.2.1 普通版

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 35, M = 210;

int m, n;
LL w[N], c[N];
LL dp[N][M];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	cin >> m >> n;
	for (int i = 1; i <= n; i++)
		cin >> w[i] >> c[i];

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			for (int k = 0; k <= j / w[i]; k++) {
				if (j - k * w[i] >= 0) {
					dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * c[i]);
				}
			}
	cout << "max=" << dp[n][m] << "\n";

	return 0;
}

1.2.2 优化版

完全背包同样可以删维。

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 35, M = 210;

int m, n;
LL w[N], c[N];
LL dp[M];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	cin >> m >> n;
	for (int i = 1; i <= n; i++)
		cin >> w[i] >> c[i];

	for (int i = 1; i <= n; i++)
		for (int j = w[i]; j <= m; j++)
			dp[j] = max(dp[j], dp[j - w[i]] + c[i]);

	cout << "max=" << dp[m] << "\n";

	return 0;
}

1.3 多重背包

多重背包新增了一个变量s,表示物品有s个。所以01背包在一定程度上也算是多重背包。

1.3.1 普通版

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1010, M = 2010;

int n, m;
LL w[N], c[N], s[N];
LL dp[N][M];

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> c[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++) {
				if (j - k * w[i] >= 0) {
					dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * c[i]);
				}
			}
		}
	}

	cout << dp[n][m] << "\n";
	
	return 0;
}

1.3.2 优化版

多重背包不仅可以删维,还可以使用二进制优化。

1.3.2.1 二进制优化概念

用 1, 2, 4, 8, 16, 32, 64 可以凑出 0 ∼ 127(= 1 + 2 + ... + 64) 的所有数字。

用 1, 2, 4, 8, 16, 32, 64, 73 可以凑出 0 ∼ 200(= 1 + 2 + ... + 64 + 73) 的所有数字。

所以,s 可以用 1, 2, 4, ..., 2 k , C (0 ≤ C < 2 k+1) 凑出, 用上述思想, 便将问题转化成了 01 背包。

1.3.2.2 二进制优化代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1010 * 13, M = 2010;

int n, m, cnt;
LL w[N], c[N];
LL dp[M];

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		int a, b, s;
		cin >> a >> b >> s;
		int k = 1;
		while (s - k >= 0) {
			s -= k;
			++cnt;
			w[cnt] = k * a;
			c[cnt] = k * b;
			k *= 2;
		}
		if (s) {
			++cnt;
			w[cnt] = s * a;
			c[cnt] = s * b;
		}
	}

	for (int i = 1; i <= cnt; i++) {
		for (int j = m; j >= w[i]; j--) {
			dp[j] = max(dp[j], dp[j - w[i]] + c[i]);
		}
	}
	cout << dp[m] << "\n";

	return 0;
}

1.4 混合背包

混合背包就是把上述背包组合起来(像废话)。

思路就是把完全背包和01背包转化为多重背包(数量分别为w%5Bi%5Deq?1个,上面也提到过)。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 40, M = 210;

int m, n;
LL w[N], c[N], p[N], dp[N][M];

int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> c[i] >> p[i];
	}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			if (p[i] == 0) {
				for (int k = 0; k <= j / w[i]; k++) {
					if (j - k * w[i] >= 0) {
						dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * c[i]);
					}
				}
			} else {
				for (int k = 0; k <= p[i]; k++) {
					if (j - k * w[i] >= 0) {
						dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * c[i]);
					}
				}
			}
		}
	cout << dp[n][m] << "\n";

	return 0;
}

2. 结尾

好了,文章内容就到这里了,谢谢大家~

 

<think>嗯,用户让我详细解释C++中的背包问题。首先,我需要确认用户对背包问题的了解程度。可能用户是刚开始学习算法,或者需要解决一个具体的问题。背包问题有很多种类型,比如01背包、完全背包、多重背包等。用户没有具体说明,所以我得先概述常见的几种类型,再重点讲解最基础的01背包问题。 接下来,我应该考虑用户可能需要的内容结构。通常,解释算法问题需要包括问题描述、基本思路、动态规划解法的详细步骤、代码实现以及时间和空间复杂度分析。用户可能还希望看到示例,帮助他们理解代码是如何工作的。 关于代码部分,必须确保语法正确,并且有适当的注释。但用户提到行内数学表达式要用$...$,独立公式用$$...$$,所以在解释动态规划的状态转移方程时,需要用LaTeX格式。比如状态转移方程dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]),需要正确格式化。 另外,用户可能对优化后的空间复杂度感兴趣,比如如何将二维数组优化为一维数组。这时候需要解释为什么可以这样做,以及遍历顺序的重要性,避免状态被覆盖。 还需要注意示例的具体数值,比如物品的重量和价值,背包的容量,以及最终的输出结果。这些例子应该简单明了,方便用户手动验证过程。 可能用户在实际编码中会遇到的一些常见错误,比如循环的顺序,或者初始化数组时的细节,这些也需要提及。例如,在一维数组优化中,内层循环必须逆序遍历,以防止重复选取同一物品。 最后,总结部分应该回顾重点,并建议用户练习相关题目来巩固理解。同时,提及其他类型的背包问题,引导用户进一步学习。 在写作过程中,要确保语言简洁,逻辑清晰,分步骤讲解,避免使用过于专业的术语,或者如果使用的话,需要适当解释。例如,动态规划中的“状态转移”可能需要简要说明其含义。 另外,检查LaTeX的格式是否正确,特别是行内公式和独立公式是否正确应用,比如max函数是否正确使用\text{max},或者直接使用\max,确保数学符号的正确显示。 还要考虑用户可能遇到的问题,比如为什么在01背包中每个物品只能选一次,而完全背包可以多次选择,这可能在代码实现上有什么不同。虽然用户的问题主要是关于01背包,但稍微提及其他类型可以帮助用户建立知识体系。 总之,我需要构建一个结构清晰、逐步深入的解答,涵盖问题定义、动态规划思路、状态转移方程、代码实现、优化方法以及示例分析,确保用户能够理解并应用所学内容。</think>### 详解C++背包问题(01背包) #### 一、问题定义 给定$n$个物品(每个物品有重量$w_i$和价值$v_i$)和容量为$C$的背包,要求选择物品装入背包,使得总价值最大,且总重量不超过背包容量。 #### 二、核心思路(动态规划) 1. **状态定义** 定义二维数组`dp[i][j]`:表示前$i$个物品在背包容量为$j$时的最大价值 2. **状态转移方程** $$ dp[i][j] = \begin{cases} \max(dp[i-1][j],\ dp[i-1][j-w_i]+v_i) & j \geq w_i \\ dp[i-1][j] & j < w_i \end{cases} $$ 3. **边界条件** $$ dp[0][j] = 0 \quad (0 \leq j \leq C) $$ #### 三、代码实现(基础版) ```cpp #include <iostream> #include <vector> using namespace std; int knapsack_01(int C, vector<int>& weights, vector<int>& values) { int n = weights.size(); vector<vector<int>> dp(n + 1, vector<int>(C + 1, 0)); for (int i = 1; i <= n; ++i) { for (int j = 1; j <= C; ++j) { if (j >= weights[i-1]) { dp[i][j] = max(dp[i-1][j], dp[i-1][j - weights[i-1]] + values[i-1]); } else { dp[i][j] = dp[i-1][j]; } } } return dp[n][C]; } ``` #### 四、空间优化(滚动数组) ```cpp int knapsack_01_optimized(int C, vector<int>& weights, vector<int>& values) { vector<int> dp(C + 1, 0); for (int i = 0; i < weights.size(); ++i) { for (int j = C; j >= weights[i]; --j) { // 必须逆向遍历 dp[j] = max(dp[j], dp[j - weights[i]] + values[i]); } } return dp[C]; } ``` #### 五、复杂度分析 - 时间复杂度:$O(nC)$ - 空间复杂度: - 基础版:$O(nC)$ - 优化版:$O(C)$ #### 六、示例演示 ```cpp int main() { vector<int> weights = {2, 3, 4, 5}; vector<int> values = {3, 4, 5, 6}; int capacity = 8; cout << "最大价值: " << knapsack_01_optimized(capacity, weights, values) << endl; // 输出:最大价值: 10(选择物品0、1、3) } ``` #### 七、关键点解析 1. **逆向遍历原理**:防止同一物品被多次选取,确保`dp[j - w]`是上一轮的状态 2. **初始化技巧**:若要求恰好装满背包,应初始化为`-∞`(除dp[0]=0) 3. **扩展变种**: - 完全背包:正序遍历容量 - 多重背包:二进制拆分优化 - 分组背包:每组最多选一个 建议通过LeetCode 416(分割等和子集)、494(目标和)等题目巩固理解。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值