分组背包问题动态规划详解:C++实现与应用

什么是分组背包?

分组背包(Group Knapsack) 是 0-1 背包的扩展。在分组背包中,物品被分成若干组,每组内的物品互斥——每组最多只能选择一件物品(或不选)。这是实际生活中常见的一类决策问题。

问题描述

有 n 组物品和一个容量为 W 的背包。第 i 组有 k[i] 件物品,第 i 组的第 j 件物品重量为 w[i][j],价值为 v[i][j]。每组最多选一件,求最大价值。

动态规划解法

定义 dp[i][w] 表示:考虑前 i 组物品,背包容量为 w 时的最大价值。

状态转移方程:

dp[i][w] = max(dp[i-1][w], dp[i-1][w - w[i][j]] + v[i][j])
          其中 j 为第 i 组的任一物品

核心思想:对于每一组,枚举组内所有物品的选择情况。

C++ 代码实现

#include <iostream>
#include <vector>
using namespace std;

// 分组背包 - 二维 DP
int group_knapsack_2d(const vector<vector<int>>& weight, 
                      const vector<vector<int>>& value, int W) {
    int n = weight.size();  // 组数
    vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));
    
    for (int i = 1; i <= n; i++) {
        for (int w = 0; w <= W; w++) {
            // 首先默认不选这一组的任何物品
            dp[i][w] = dp[i-1][w];
            
            // 枚举第 i 组的所有选择
            for (int j = 0; j < weight[i-1].size(); j++) {
                if (w >= weight[i-1][j]) {
                    dp[i][w] = max(dp[i][w], 
                        dp[i-1][w - weight[i-1][j]] + value[i-1][j]);
                }
            }
        }
    }
    
    return dp[n][W];
}

// 分组背包 - 一维 DP(空间优化)
int group_knapsack_1d(const vector<vector<int>>& weight, 
                      const vector<vector<int>>& value, int W) {
    int n = weight.size();
    vector<int> dp(W + 1, 0);
    
    for (int i = 0; i < n; i++) {
        // 逆序遍历容量(0-1背包特性,每组最多选一件)
        for (int w = W; w >= 0; w--) {
            // 枚举第 i 组的所有选择
            for (int j = 0; j < weight[i].size(); j++) {
                if (w >= weight[i][j]) {
                    dp[w] = max(dp[w], 
                        dp[w - weight[i][j]] + value[i][j]);
                }
            }
        }
    }
    
    return dp[W];
}

// 记录具体的选择方案
struct Choice {
    int group_idx;
    int item_idx;
};

vector<Choice> group_knapsack_with_choice(const vector<vector<int>>& weight,
                                          const vector<vector<int>>& value, int W) {
    int n = weight.size();
    vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));
    
    // 填充 DP 表
    for (int i = 1; i <= n; i++) {
        for (int w = 0; w <= W; w++) {
            dp[i][w] = dp[i-1][w];
            for (int j = 0; j < weight[i-1].size(); j++) {
                if (w >= weight[i-1][j]) {
                    dp[i][w] = max(dp[i][w], 
                        dp[i-1][w - weight[i-1][j]] + value[i-1][j]);
                }
            }
        }
    }
    
    // 回溯找出选择
    vector<Choice> choices;
    int w = W;
    for (int i = n; i > 0 && w > 0; i--) {
        for (int j = 0; j < weight[i-1].size(); j++) {
            if (w >= weight[i-1][j] && 
                dp[i][w] == dp[i-1][w - weight[i-1][j]] + value[i-1][j]) {
                choices.push_back({i-1, j});
                w -= weight[i-1][j];
                break;  // 找到这一组的选择后退出
            }
        }
    }
    
    return choices;
}

int main() {
    // 示例:3组物品
    // 第0组:{手机, 平板} - 只能选一个
    // 第1组:{耳机, 音箱} - 只能选一个  
    // 第2组:{键盘, 鼠标} - 只能选一个
    
    vector<vector<int>> weight = {
        {2, 3},   // 第0组:重量2或3
        {1, 4},   // 第1组:重量1或4
        {2, 1}    // 第2组:重量2或1
    };
    
    vector<vector<int>> value = {
        {3000, 4000},  // 第0组:价值3000或4000
        {500, 1500},   // 第1组:价值500或1500
        {400, 200}     // 第2组:价值400或200
    };
    
    int W = 5;  // 背包容量
    
    cout << "分组背包示例:" << endl;
    cout << "背包容量: " << W << endl;
    cout << "\n各组物品:" << endl;
    for (int i = 0; i < weight.size(); i++) {
        cout << "第" << i << "组: ";
        for (int j = 0; j < weight[i].size(); j++) {
            cout << "[w=" << weight[i][j] << ",v=" << value[i][j] << "] ";
        }
        cout << endl;
    }
    
    cout << "\n最大价值 (二维DP): " << group_knapsack_2d(weight, value, W) << endl;
    cout << "最大价值 (一维DP): " << group_knapsack_1d(weight, value, W) << endl;
    
    // 输出选择方案
    vector<Choice> choices = group_knapsack_with_choice(weight, value, W);
    cout << "\n选择的物品: ";
    for (auto& c : choices) {
        cout << "(组" << c.group_idx << ", 物品" << c.item_idx 
             << ": w=" << weight[c.group_idx][c.item_idx] 
             << ", v=" << value[c.group_idx][c.item_idx] << ") ";
    }
    cout << endl;
    
    return 0;
}

复杂度分析

方法时间复杂度空间复杂度
二维 DPO(n × W × m)O(n × W)
一维 DPO(n × W × m)O(W)

其中 n 是组数,W 是容量,m 是平均每组物品数。

分组背包的典型应用

  1. 旅行打包:不同类别的物品,每类最多带一件
  2. 预算分配:不同类型的投资,每类选择一个项目
  3. 课程安排:不同时段的课程,每时段选一门
  4. 游戏装备:不同部位的装备,每部位最多穿一件

总结

分组背包是 0-1 背包的自然扩展,核心在于组内互斥的约束。解题时只需在 0-1 背包的基础上,对每一组内的物品进行枚举即可。空间优化技巧与 0-1 背包相同,使用一维数组时需要逆序遍历容量。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇