多重背包问题
有 \(N\)种物品和一个容量是 \(V\)的背包。
第 \(i\)种物品最多有 \(s_i\) 件,每件体积是 \(v_i\),价值是 \(w_i\)。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
滑动窗口优化 \(O(n \times v)\)
\(f[i][j] = max(f[i - 1][j],f[i - 1][j - v_i] + w_i, f[i - 1][j - 2v_i] + 2w_i,...,f[i-1][j -s_iv_i] + s_iw_i)\)
\(f[i][j - v_i] = max(f[i - 1][j - v_i], f[i - 1][j - 2v_i] + w_i, ...,f[i - 1][j - (s_i + 1)v_i] + s_iw_i)\)
\(f[i][j - 2v_i] = max(f[i - 1][j - 2v_i], f[i - 1][j - 3v_i] + w_i,...,f[i - 1][j - (s_i + 2)v_i] + s_iw_i)\)
\(...\)
\(f[i][r + (s_i + 1)v_i] = max(f[i - 1][r + (s_i + 1)v_i], f[i - 1][r + (s_i)v_i] + w_i, ...,f[i - 1][r + v_i] + s_iw_i)\)
\(f[i][r + s_iv_i] = max(f[i - 1][r + s_iv_i], f[i - 1][r + (s_i - 1)v_i] + w_i, ... , f[i - 1][r] + s_iw_i)\)
\(f[i][r + (s_i - 1)v_i] = max(f[i - 1][r + (s_i - 1)v_i], f[i - 1][r + (s_i - 2)v_i] + w_i, ..., f[i -1][r] + (s_i - 1)w_i)\)
\(...\)
\(f[i][r + 2v_i] = max(f[i - 1][r + 2v_i], f[i - 1][r + v_i] + w_i, f[i - 1][r] + 2w_i)\)
\(f[i][r + v_i] = max(f[i - 1][r + v_i], f[i - 1][r] + w_i)\)
\(f[i][r] = f[i - 1][r]\)
\(r \space 表示 \space j \bmod v_i\)
我们从\(r\)开始递推,考虑到\(f[i][x]\) 与\(f[i][x - v_i]\)的每个\(f[i - 1][y]\)偏移量同等的差\(w\), 因此不影响我们比较大小,这里举个例子我们在求\(f[i][r + v_i]\)时,假设\(f[i - 1][r + v_i] > f[i -1][r] + w_i\),那么递推到\(f[i][r + 2v_i]\)时, \(f[i - 1][r + v_i] + w_i > f[i - 1][r + r] + 2w_i\) 同样成立,我们只需要通过前缀的\(max\)与\(f[i - 1][r + 2v_i]\)比较即可。
另外由于物品数量有限,因此对于\(x\), \(x \ge r + s_iv_i\), 它的集合中只包含取\([0, s_iv_i]\)的物品数量。
例如,\(f[i - 1][r + (s_i + 1)v_i]\)的集合中并不包含\(f[i][r]\), 而是包含区间\(f[i -1][x], x \in [r + v_i, r + (s_i + 1)v_i]\),
因此我们可以用滑动窗口来处理一个窗口大小为\(s_i + 1\)个元素的窗口,来求最大值。
复杂度:\(O(n \times v)\)
滑动窗口优化Code
二维
#include <iostream>
const int N = 1e3 + 10, M = 2e4 + 10;
int q[M];
int f[N][M], v[N], w[N], s[N];
int n, m;
int main() {
std::cin >> n >> m;
for(int i = 1; i <= n; i ++) {
std::cin >> v[i] >> w[i] >> s[i];
}
for(int i = 1; i <= n; i ++) {
for(int r = 0; r < v[i]; r ++) {
int hh = 0, tt = -1;
for(int j = r; j <= m; j += v[i]) {
if(hh <= tt && j - q[hh] > s[i] * v[i]) hh ++;
while(hh <= tt && f[i - 1][q[tt]] + (j - q[tt]) / v[i] * w[i] <= f[i - 1][j]) tt --;
q[++ tt] = j;
f[i][j] = f[i - 1][q[hh]] + (j - q[hh]) / v[i] * w[i];
}
}
}
std::cout << f[n][m];
}
拷贝数组版
#include <iostream>
#include <cstring>
const int N = 1e3 + 10, M = 2e4 + 10;
int q[M];
int f[M], g[M], v[N], w[N], s[N];
int n, m;
int main() {
std::cin >> n >> m;
for(int i = 1; i <= n; i ++) {
std::cin >> v[i] >> w[i] >> s[i];
}
for(int i = 1; i <= n; i ++) {
memcpy(g, f, sizeof f);
for(int r = 0; r < v[i]; r ++) {
int hh = 0, tt = -1;
for(int j = r; j <= m; j += v[i]) {
if(hh <= tt && j - q[hh] > s[i] * v[i]) hh ++;
while(hh <= tt && g[q[tt]] + (j - q[tt]) / v[i] * w[i] <= g[j]) tt --;
q[++ tt] = j;
f[j] = g[q[hh]] + (j - q[hh]) / v[i] * w[i];
}
}
}
std::cout << f[m];
}
一维 (实际上是奇偶交替的,减少了拷贝数组的时间复杂度)
#include <iostream>
#include <cstring>
const int N = 1e3 + 10, M = 2e4 + 10;
int q[M];
int f[2][M], v[N], w[N], s[N];
int n, m;
int main() {
std::cin >> n >> m;
for(int i = 1; i <= n; i ++) {
std::cin >> v[i] >> w[i] >> s[i];
}
for(int i = 1; i <= n; i ++) {
for(int r = 0; r < v[i]; r ++) {
int hh = 0, tt = -1;
for(int j = r; j <= m; j += v[i]) {
if(hh <= tt && j - q[hh] > s[i] * v[i]) hh ++;
while(hh <= tt && f[(i - 1) & 1][q[tt]] + (j - q[tt]) / v[i] * w[i] <= f[(i - 1) & 1][j]) tt --;
q[++ tt] = j;
f[i & 1][j] = f[(i - 1) & 1][q[hh]] + (j - q[hh]) / v[i] * w[i];
}
}
}
std::cout << f[n & 1][m];
}
二进制优化 \(O(n^2log(S))\)
考虑一种物品\(s_i\), 我们将\(s_i\) 拆分成若干个物品组合而成, 例如\(s_i = 11\), 则其可以拆分为\(1, 2, 4, 4\) 来表示区间\([0, 11]\), 即可以理解为\((0111)_2\)可以表示\([0, 7]\), 我们给这个区间加上一个偏移量\(k = s_i - (0111)_2 = 11 - 7 = 4\), 这时能表示的区间就变成了\([0, 11]\), 我们不考虑\(4\)给以前区间出现重叠的情况,即出现多种组合表示一个数,因为这样的情况同样合法。
Code
#include <iostream>
using i64 = long long;
const int N = 1e3 + 10, M = 2e4 + 10;
int n, m;
int dp[N * 32], v[N * 32], w[N * 32], cnt;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> m;
for(int i = 1; i <= n; i ++) {
int s1, v1, w1;
std::cin >> v1 >> w1 >> s1;
for(int k = 1; k <= s1; k <<= 1) {
v[++ cnt] = k * v1, w[cnt] = k * w1;
s1 -= k;
}
if(s1 > 0) {
v[++ cnt] = s1 * v1, w[cnt] = s1 * w1;
}
}
for(int i = 1; i <= cnt; i ++) {
for(int j = m; j >= v[i]; j --) {
dp[j] = std::max(dp[j], dp[j - v[i]] + w[i]);
}
}
std::cout << dp[m];
}