算法 | 动态规划背包模型 - A
通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(分治)的方式去解决:动态规划 (Dynamic Programming, DP)
01 背包模型
例题
来源:AcWing02 - 01背包问题 - https://www.acwing.com/problem/content/description/2/
有
第
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,
接下来有
输出格式
输出一个整数,表示最大价值。
数据范围
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
8
分析
状态表示
- 集合:满足“在前
个物品选择,总体积 ”的条件的所有选法的集合。 - 属性:集合中选法价值的最大值。
综上,状态表示为:
状态计算
集合划分:
划分 | 结果 |
---|---|
从 | |
从 |
因此,结果就是两个划分中的最大值,即
解析:
如果在第
如果在第
代码
朴素版本
- 时间复杂度:
- 空间复杂度:
#include <iostream>
#include <algorithm>
using namespace std;
const int SIZE = 1010;
int N, V; // N-物品数量 V-背包容积
int v[SIZE], w[SIZE]; // v-体积 w-价值
int dp[SIZE][SIZE]; // 二维动态规划
int main()
{
cin >> N >> V;
for (int i = 1; i <= N; i++)
cin >> v[i] >> w[i];
for (int i = 1; i <= N; i++)
{
for (int j = 0; j <= V; j++)
{
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]);
}
}
cout << dp[N][V] << endl;
return 0;
}
空间优化
上方代码可等价变形为此代码,将二维动态规划优化为了一维动态规划。原因是每一层 dp 时,只用到了上一层 dp 的数据,之前的数据是无用的。因此可以用滚动数组,计算出的新结果可以直接覆盖掉上一层的结果,这样只用一维数组就能解决该问题。
但直接改为滚动数组还有一个问题,第二层循环计算到
要解决这个问题也很简单,我们可以反向循环,从大到小覆盖,这样就能保证我们需要用的值不被覆盖。
- 时间复杂度:
- 空间复杂度:
#include <iostream>
#include <algorithm>
using namespace std;
const int SIZE = 1010;
int N, V; // N-物品数量 V-背包容积
int v[SIZE], w[SIZE]; // v-体积 w-价值
int dp[SIZE]; // 一维动态规划
int main()
{
cin >> N >> V;
for (int i = 1; i <= N; i++)
cin >> v[i] >> w[i];
for (int i = 1; i <= N; i++)
for (int j = V; j >= v[i]; j--)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
cout << dp[V] << endl;
return 0;
}
完全背包模型
例题
来源:AcWing03 - 完全背包问题 - https://www.acwing.com/problem/content/description/3/
有
第
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,
接下来有
输出格式
输出一个整数,表示最大价值。
数据范围
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
10
分析
状态表示
- 集合:满足“在前
个物品选择,总体积 ”的条件的所有选法的集合。 - 属性:集合中选法价值的最大值。
综上,状态表示为:
状态计算
集合划分:
划分 | 结果 |
---|---|
从 | |
从 | |
从 | |
从 |
这
因此,
代码
朴素版本
- 时间复杂度:
(最坏情况) - 空间复杂度:
#include <iostream>
#include <algorithm>
using namespace std;
const int SIZE = 1010;
int N, V;
int v[SIZE], w[SIZE];
int dp[SIZE][SIZE];
int main()
{
cin >> N >> V;
for (int i = 1; i <= N; i++)
cin >> v[i] >> w[i];
for (int i = 1; i <= N; i++)
for (int j = 0; j <= V; j++)
for (int k = 0; 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][V] << endl;
return 0;
}
时间优化
通过观察
- 时间复杂度:
- 空间复杂度:
#include <iostream>
#include <algorithm>
using namespace std;
const int SIZE = 1010;
int N, V;
int v[SIZE], w[SIZE];
int dp[SIZE][SIZE];
int main()
{
cin >> N >> V;
for (int i = 1; i <= N; i++)
cin >> v[i] >> w[i];
for (int i = 1; i <= N; i++)
{
for (int j = 0; j <= V; j++)
{
dp[i][j] = dp[i - 1][j];
if (j >= v[i])
dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
}
}
cout << dp[N][V] << endl;
return 0;
}
空间优化
思想和上面的 01 背包一模一样。但此处不能反向循环,得正向循环,因为
- 时间复杂度:
- 空间复杂度:
#include <iostream>
#include <algorithm>
using namespace std;
const int SIZE = 1010;
int N, V;
int v[SIZE], w[SIZE];
int dp[SIZE];
int main()
{
cin >> N >> V;
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 <= V; j++)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
cout << dp[V] << endl;
return 0;
}
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi