多重背包问题两种解法
c++
多重背包问题
/*
* 多重背包问题
* 问题描述:
* 有 N 种物品和一个容量是 V 的背包。
* 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。(这样的话和 01 背包和完全背包都不一样)
* 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
* 输出最大价值。
*
* 数据范围:
* 0 < N, V ≤ 100
* 0 < vi,wi,si ≤ 100
* 背包问题都是算法的经典题目,下面给出 多重背包问题 的解题思路:
*
* 解题思路1 朴素解法:
* 数组定义:
* f[i][j] 表示前 i 个物品,体积为小于等于 j 的最大价值
* 递归方程:
* f[i][j] = max(f[i-1][j], f[i-1][j-vi]+wi, ..., f[i-1][j-kvi]+kwi k<=j//vi && k <= si)
* 初始化结果:
* f[0][0] = 0
* 最终结果
* max(f[n][0], f[n][1], ..., f[n][m])
* 复杂度分析:
* 时间复杂度为 O(NMS)
*
* 解题思路2 多重背包转01背包
* 算法思路:
* 因为朴素方法的多重背包复杂度过高 O(NMS) 考虑使用 01背包方法优化,思路主要从将 s_i 拆分入手
* 如 17 个物品,可以转换为 1个、2个、4个、8个、2个 的组合,他们的 01背包。
* 因为 任何小于等于 17 的东西,可以由 (1, 2, 4, 8, 2) 组合得到,任何大于 17 都不能被组合成功。
*
* 拆分 x:
* 首先重 1, 2, 4, 8 ... 一直到 x 无法被细分,剩余的 x 部分,再塞进去
*
* x -> (1, 2, 4, 2 ^ k, x - (2 ^ (k + 1) - 1)),并且 x - (2 ^ (k + 1) - 1) <= 2 ^ (k + 1)
* 而且从算法的角度来看 (1, 2, 4, 2 ^ k) 的组合,囊括了 [1, 2 ^ (k + 1) - 1] 的所有点。
* x - (2 ^ (k + 1) - 1) 起到的是平移区间左右,使得其可以 到达 x 的范围。
*
* 因为拆出来的 n 数量稍多,建议使用空间优化的方案
*
* 复杂度:
* O(NMlogS)
*
*/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <cstdio>
using namespace std;
const int N = 1010, M = 2010;
int n, m;
int v[N], w[N], s[N];
int f[N][M];
int solution_one() {
// initial
memset(f, 0, sizeof f);
// 递推方程
for (int i = 1; i <= n; i ++ ) {
for (int j = 0; j <= m; j ++ ) {
for (int k = 0; k <= s[i] && k <= j / v[i]; k ++ ) {
f[i][j] = max(f[i][j], f[i-1][j-k*v[i]] + k*w[i]);
}
}
}
// 返回结果
return f[n][m];
}
int solution_two() {
int v01[N * 20], w01[N * 20];
int n1 = 0, k;
// split to 01
for (int i = 1; i <= n; i ++ ) {
k = 1;
while (s[i] >= k) {
s[i] -= k;
n1 += 1;
v01[n1] = v[i] * k;
w01[n1] = w[i] * k;
k *= 2;
}
if (s[i] != 0) {
n1 += 1;
v01[n1] = v[i] * s[i];
w01[n1] = w[i] * s[i];
}
}
int f[M];
memset(f, 0, sizeof f);
for (int i = 1; i <= n1; i ++ ){
for (int j = m; j >= v01[i]; j -- ) {
f[j] = max(f[j], f[j - v01[i]] + w01[i]);
}
}
return f[m];
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) {
scanf("%d%d%d", &v[i], &w[i], &s[i]);
}
int res = solution_two();
printf("%d\n", res);
return 0;
}