背包问题详细解析
01背包问题
1.经典例题
有
第
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
2.解题思路
先给出一种较为暴力的二维动态规划做法:
用
那么我们的答案就是选前
然后我们考虑
-
1.不选第
个物品,那么其价值和体积与上一个物品时一样,即: -
2.选第
个物品,那么其价值就是只看前 个物品且当前体积为 时的价值加上当前物品的价值,即
那么我们的
但是我们想一想,如果只有这两个公式,那么
然后我们不难发现,当一个物品也不考虑,且体积为
代码如下
#include <iostream>
#include <algorithm>
#include <cstring>
//#define int long long
using namespace std;
#define N 1010
#define For(i,j,k) for(int i=j;i<=k;i++)
#define IOS ios::sync_with_stdio(0),cin.tie(),cout.tie()
int n, m, v[N], w[N], dp[N][N];
int main () {
IOS;
cin >> n >> m;
For (i, 1, n) cin >> v[i] >> w[i];
dp[0][0] = 0;
For (i, 1, n) {
For (j, 0, m) {
dp[i][j] = dp[i - 1][j];
if (j >= v[i])
dp[i][j] = max (dp[i][j], dp[i - 1][j - v[i]] + w[i]);
}
}
int ans = 0;
For (i, 0, m) {
ans = max (ans, dp[n][i]);
}
cout << ans << endl;
return 0;
}
然后我们再来考虑将二维改为一维去优化它:
我们发现在转移第
dp[i][j] = dp[i - 1][j];
dp[i][j] = max (dp[i][j], dp[i - 1][j - v[i]] + w[i]);
因为我们是消掉了数组的第一维,所以第一个方程可以直接去掉,但是对于第二个方程,如果强行滚掉第一维,肯定是不可取的。因为我们在计算时会用到
我们不难发现,在枚举
所以我们可以倒着枚举,从大枚举到小,那样就能保证
所以我们就只需要一个状态转移方程:
dp[j] = max (dp[j], dp[j - v[i]] + w[i]);
而最后的枚举答案的循环也可以被省掉,答案就是
代码如下
#include <iostream>
#include <algorithm>
#include <cstring>
//#define int long long
using namespace std;
#define N 1010
#define For(i,j,k) for(int i=j;i<=k;i++)
#define IOS ios::sync_with_stdio(0),cin.tie(),cout.tie()
int n, m, v[N], w[N], dp[N];
int main () {
IOS;
cin >> n >> m;
For (i, 1, n) cin >> v[i] >> w[i];
For (i, 1, n) {
for (int j = m; j >= 0; j --) {
if (j >= v[i])
dp[j] = max (dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[m] << endl;
return 0;
}
2.完全背包问题
1.经典例题
有
第
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
2.解题思路
不难发现,完全背包问题其实就是在01背包问题的基础上将 最多选一次
变成了 可以选无限次
。
那么我们仍然可以用
然后对于每个
然后我们来分情况讨论这两种选法:
-
1.不选第
个,即 -
2.选第
个且选择了 个,即
最后答案就是两者的较大值。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m, v[N], w[N], f[N][N];
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 = 0; j <= m; j ++)
for (int k = 0; k * v[i] <= j; k ++)
f[i][j] = max (f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
cout << f[n][m] << endl;
return 0;
}
但是很明显,代码最多有三层循环,时间复杂度
所以我们要想办法优化掉一层循环。
我们将状态转移方程展开,发现:
然后我们把
所以我们发现,一式后面所有项的最大值实际上就是二式的
所以化简之后的方程式为:
这样一来时间久被优化到两层
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m, v[N], w[N], f[N][N];
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 = 0; j <= m; j ++)
if (j - v[i] >= 0)
f[i][j] = max (f[i - 1][j], f[i][j - v[i]] + w[i]);
else f[i][j] = f[i - 1][j];
cout << f[n][m] << endl;
return 0;
}
然后和
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m, v[N], w[N], f[N];
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 = v[i]; j <= m; j ++)
//注意这里实际上是 j 从 0~m 循环,然后判断 j - v[i] 是否大于等于0,简写成上面这样是等效的
f[j] = max (f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!