题目链接

【模板】多重背包

题目描述

给定 种物品和一个容量为 的背包。第 种物品的体积是 ,价值是 ,并且数量最多有 件。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输入:

  • 第一行输入一个整数 ,表示有 组测试数据。
  • 对于每组测试数据:
    • 第一行输入两个整数 ,分别表示物品种类和背包容量。
    • 接下来 行,每行输入三个整数 ,分别表示第 种物品的体积、价值和数量。

输出:

  • 对于每组测试数据,输出一个整数,表示能获得的最大总价值。

解题思路

本题是典型的 多重背包问题。它介于01背包和完全背包之间:每种物品可以选择多次,但有数量上限。

  1. 朴素解法 (超时):

    • 最直接的想法是将每种物品看作 个独立的、相同的物品,然后用01背包的方法解决。但如果 很大,物品总数会非常多,导致超时。
  2. 二进制拆分优化:

    • 我们可以对物品数量 进行优化。核心思想是,任何一个在 范围内的整数,都可以由 以及一个余数 的和来表示。
    • 例如,如果一种物品有 件,我们可以将其拆分为4个新的“物品包”:
      • 数量为 的包:体积 ,价值
      • 数量为 的包:体积 ,价值
      • 数量为 的包:体积 ,价值
      • 数量为 的包:体积 ,价值
    • 通过选择这几个包的组合,我们可以凑出原物品数量从 的任意件。
    • 这样,我们将原来的一种物品(数量为 )拆分成了 个新的物品,每个新物品只能选一次。
  3. 转化为01背包:

    • 通过二进制拆分,我们将多重背包问题成功转化为了一个 01背包问题。新物品的总数是
    • 接下来,我们应用标准的01背包解法即可。
    • 状态定义: 表示容量为 的背包能获得的最大价值。
    • 状态转移: 对于每个拆分出的新物品(体积 , 价值 ),我们更新 数组:
    • 注意:01背包问题的内层循环(背包容量)必须 逆序遍历,以保证每个物品只被选择一次。

代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct Item {
    int v, w;
};

int main() {
    int T;
    cin >> T;
    while (T--) {
        int N, V;
        cin >> N >> V;
        vector<Item> items;
        for (int i = 0; i < N; ++i) {
            int v, w, c;
            cin >> v >> w >> c;
            // 二进制拆分
            for (int k = 1; k <= c; k *= 2) {
                items.push_back({v * k, w * k});
                c -= k;
            }
            if (c > 0) {
                items.push_back({v * c, w * c});
            }
        }

        vector<long long> dp(V + 1, 0);
        
        // 01背包
        for (const auto& item : items) {
            for (int j = V; j >= item.v; --j) {
                dp[j] = max(dp[j], dp[j - item.v] + item.w);
            }
        }
        cout << dp[V] << "\n";
    }
    return 0;
}
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    static class Item {
        int v, w;
        Item(int v, int w) {
            this.v = v;
            this.w = w;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        while (T-- > 0) {
            int N = sc.nextInt();
            int V = sc.nextInt();
            List<Item> items = new ArrayList<>();
            for (int i = 0; i < N; i++) {
                int v = sc.nextInt();
                int w = sc.nextInt();
                int c = sc.nextInt();
                // 二进制拆分
                for (int k = 1; k <= c; k *= 2) {
                    items.add(new Item(v * k, w * k));
                    c -= k;
                }
                if (c > 0) {
                    items.add(new Item(v * c, w * c));
                }
            }

            long[] dp = new long[V + 1];

            // 01背包
            for (Item item : items) {
                for (int j = V; j >= item.v; j--) {
                    dp[j] = Math.max(dp[j], dp[j - item.v] + item.w);
                }
            }
            System.out.println(dp[V]);
        }
    }
}
T = int(input())
for _ in range(T):
    N, V = map(int, input().split())
    items = []
    for i in range(N):
        v, w, c = map(int, input().split())
        # 二进制拆分
        k = 1
        while k <= c:
            items.append((v * k, w * k))
            c -= k
            k *= 2
        if c > 0:
            items.append((v * c, w * c))

    dp = [0] * (V + 1)
    
    # 01背包
    for v_item, w_item in items:
        for j in range(V, v_item - 1, -1):
            dp[j] = max(dp[j], dp[j - v_item] + w_item)

    print(dp[V])

算法及复杂度

  • 算法:动态规划 (多重背包 + 二进制拆分优化)
  • 时间复杂度: - 为数据组数, 为背包容量, 为第 种物品的数量。
  • 空间复杂度: - 主要开销来自 数组和存储拆分后物品的列表。