多重背包问题两种解法

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;
}

posted @ 2022-07-02 15:01  lucky_light  阅读(223)  评论(0编辑  收藏  举报