算法 | 动态规划背包模型 - B
通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(分治)的方式去解决:动态规划 (Dynamic Programming, DP)
多重背包模型
例题
来源:AcWing04 - 多重背包问题 I - https://www.acwing.com/problem/content/4/
有
第
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,
接下来有
输出格式
输出一个整数,表示最大价值。
数据范围
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例
10
分析 & 代码
暴力解法
多重背包和完全背包的区别就是每一种物品取得数量有一个上限
- 时间复杂度:
(最坏情况) - 空间复杂度:
#include <iostream>
#include <algorithm>
using namespace std;
const int SIZE = 1010;
int N, V;
int v[SIZE], w[SIZE], s[SIZE];
int dp[SIZE];
int main()
{
cin >> N >> V;
for (int i = 1; i <= N; i++)
cin >> v[i] >> w[i] >> s[i];
for (int i = 1; i <= N; i++)
for (int j = V; j >= v[i]; j--)
for (int k = 0; k * v[i] <= j && k <= s[i]; k++)
dp[j] = max(dp[j], dp[j - k * v[i]] + k * w[i]);
cout << dp[V] << endl;
return 0;
}
二进制优化
暴力解法中第三层循环从
一个数
那么对于任意一个数
通过这种思想,我们改变第三层循环的方式,不去一个一个取物品,而是将物品每
- 时间复杂度:
- 空间复杂度:
(已包含空间优化)
#include <iostream>
#include <algorithm>
using namespace std;
// 需要注意各个数组的大小
const int SIZE_NlogS = 25000, SIZE_V = 2010;
int N, V, idx;
int v[SIZE_NlogS], w[SIZE_NlogS];
int dp[SIZE_V];
int main()
{
cin >> N >> V;
// 物品读入
for (int i = 1; i <= N; i++)
{
int a, b, s;
cin >> a >> b >> s;
int k = 1;
// 物品打包
while (k <= s)
{
v[++idx] = a * k;
w[idx] = b * k;
s -= k;
k *= 2;
}
if (s)
{
v[++idx] = a * s;
w[idx] = b * s;
}
}
// 01-背包问题
for (int i = 1; i <= idx; 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;
}
单调队列优化
对于完全背包问题,我们每轮能够取到的数量
但对于多重背包模型,我们每轮能够取到的数量有上界
此时我们会发现,从
不过,这个式子满足滑动窗口的性质,我们相当于每次都维护一个长度为
其中,单调递减队列储存
我们可以发现
即单调队列比较时使用:
- 时间复杂度:
- 空间复杂度:
(已包含空间优化)
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
int main()
{
int N, V;
cin >> N >> V;
vector<int> v(N + 10), w(N + 10), s(N + 10);
for (int i = 1; i <= N; i++)
cin >> v[i] >> w[i] >> s[i];
vector<int> dp(V + 10), lst(V + 10), que(V + 10);
for (int i = 1; i <= N; i++)
{
lst = dp;
for (int r = 0; r < v[i]; r++)
{
int hh = 0, tt = -1;
for (int k = r; k <= V; k += v[i])
{
if (hh <= tt && que[hh] < k - s[i] * v[i])
++hh;
while (hh <= tt && lst[que[tt]] - (que[tt] - r) / v[i] * w[i] <= lst[k] - (k - r) / v[i] * w[i])
--tt;
if (hh <= tt)
dp[k] = max(dp[k], lst[que[hh]] - (que[hh] - k) / v[i] * w[i]);
que[++tt] = k;
}
}
}
cout << dp[V] << endl;
return 0;
}
分组背包问题
例题
来源:AcWing09 - 分组背包问题 - https://www.acwing.com/problem/content/9/
有
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数
接下来有
- 每组数据第一行有一个整数
,表示第 个物品组的物品数量; - 每组数据接下来有
行,每行有两个整数 ,用空格隔开,分别表示第 个物品组的第 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例
8
分析
实际仍然能当作“01-背包问题”来看,只不过“01-背包问题”的每一个物品变成了一组物品,不过我们只用遍历这一组,求到这一组物品的最优情况即可。
- 时间复杂度:
- 空间复杂度:
(已包含空间优化)
#include <bits/stdc++.h>
using namespace std;
const int SIZE = 110;
int N, V;
int v[SIZE][SIZE], w[SIZE][SIZE], S[SIZE];
int dp[SIZE];
int main()
{
cin >> N >> V;
for (int i = 1; i <= N; i++)
{
cin >> S[i];
for (int j = 0; j < S[i]; j++)
cin >> v[i][j] >> w[i][j];
}
for (int i = 0; i <= N; i++)
for (int j = V; j >= 0; j--)
for (int k = 0; k < S[i]; k++)
if (v[i][k] <= j)
dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
cout << dp[V] << endl;
return 0;
}
混合背包问题
例题
有
物品一共有三类:
- 第一类物品只能用
次(01背包); - 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用
次(多重背包);
每种体积是
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,
接下来有
表示第 种物品只能用 次; 表示第 种物品可以用无限次; 表示第 种物品可以使用 次;
输出格式
输出一个整数,表示最大价值。
数据范围
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例
8
分析
对于混合背包问题,我们的处理方式其实就是对于每一个物品对应的背包模型,使用对应的处理方式。
对于完全背包,我们直接用完全背包的计算方式即可。对于多重背包问题,我们使用二进制优化方式转化为 0-1 背包问题,与 0-1 背包一起统一处理即可。
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
void solve()
{
int N, V;
cin >> N >> V;
vector<int> dp(V + 10);
for (int i = 1; i <= N; i++)
{
int v, w, s;
cin >> v >> w >> s;
if (s == 0) // 完全背包
{
for (int j = v; j <= V; j++)
dp[j] = max(dp[j], dp[j - v] + w);
}
else // 0-1背包 和 多重背包
{
s = abs(s); // 一律当多重背包转化成0-1背包
vector<int> vv, ww;
int k = 1;
while (k <= s)
{
vv.push_back(v * k);
ww.push_back(w * k);
s -= k;
k *= 2;
}
if (s)
{
vv.push_back(v * s);
ww.push_back(w * s);
}
for (int j = 0; j < vv.size(); j++)
for (int k = V; k >= vv[j]; k--)
dp[k] = max(dp[k], dp[k - vv[j]] + ww[j]);
}
}
cout << dp[V] << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi