【华为od刷题(C++)】HJ16 购物单(动态规划、0-1 背包问题、二维数组)

我的代码:

#include 
#include 
#include //包含向量库,程序中的数据结构主要使用了 vector 来存储和处理数据

using namespace std;

int main() {
    int N, m;//N 是背包的容量(单位是10),m 是物品的数量
    cin >> N >> m;

    vector> v(m + 1, vector(3, 0));  
    /* 该行代码创建了一个二维 vector,
    总共有 m + 1 行,每行有 3 个元素,且每个元素的初始值都为 0 

    vector:表示一个动态数组容器,存储整数,可以根据需要调整大小

    vector>:这是一个二维容器,实际上是一个由多个 vector 组成的数组,
    可以理解为一个矩阵,其中每个元素是一个 vector

    m + 1:表示二维数组有 m + 1 行(第一个维度的大小)
    这里 m 表示物品的数量,m + 1 是因为要包含 0 到 m 的索引,通常是为了方便处理边界条件

    vector(3, 0):表示每一行是一个长度为 3 的 vector,并且每个元素的初始值都设置为 0
    每一行有 3 个元素,分别用来存储一个物品的三种价格(主件价格和两个附件的价格)

    v[i][0] 存储第 i 个物品的主件价格。
    v[i][1] 和 v[i][2] 分别存储附件 1 和附件 2 的价格 */

    vector> w(m + 1, vector(3, 0));  
    /* 这行代码类似于 v 的定义,创建了另一个 二维动态数组 w,
    它用于存储 物品的价格和重要度的乘积,即 满意度
    结构和初始化方式与 v 相同,只是它的用途不同:

    w[i][0] 存储第 i 个物品主件的 价格乘以重要度(满意度)
    w[i][1] 和 w[i][2] 分别存储附件 1 和附件 2 的 价格乘以重要度(满意度)*/

    vector> dp(m + 1, vector(N + 1, 0)); 
    /* 这行代码创建了一个 二维动态数组 dp,用于动态规划的计算,特别是在背包问题中用于存储最优解解
    dp 数组的作用通常是保存状态,表示在考虑了前 i 个物品,并且背包容量为 j 时,所能获得的最大价值      (这里指最大满意度)
    m + 1 表示有 m + 1 行
    N + 1 表示每行有 N + 1 列,这里的 N 是背包容量,N + 1 是为了方便处理容量为 0 到 N 的情况
    每个元素的初始值为 0,表示初始状态下的最大满意度 */

    N /= 10;
    //将背包容量单位化,转换成整数(这是因为输入的容量是10的倍数)

    for (int i = 1; i < m + 1; i++) { 
    // 物品编号从1开始,q为0表示主件,为1表示第1件物品的附件

        int vv, p, q;
        //vv(价格),p(重要度),q(是否是附件)

        cin >> vv >> p >> q;

        vv /= 10; // 简化计算,加快计算速度

        if (q) {  // 如果q不为0,则说明第i个物品是附件
            if (w[q][1]) { // 如果w[q][1]不为0,则说明不是第一个附件
                v[q][2] = vv; 
                w[q][2] = vv * p;
            } else { // 否则就是第一个附件
                v[q][1] = vv;
                w[q][1] = vv * p;
            }
        } else { // 否则,第i个物品是主件
            v[i][0] = vv;
            w[i][0] = vv * p;
        }
        /* w[q][1] 存储的是第 q 个物品的第一个附件的满意度(即附件1的价格 * 重要度)
        如果 w[q][1] 不为0,说明这个附件的满意度已经被赋值,即说明附件1已经被处理过

        如果 w[q][1] 的值为0,那么这个附件还没有被赋值,因此可以确定它是第一个附件,
        此时就可以将它存储为附件1的价格和满意度

        如果 w[q][1] 的值不为0,就说明第一个附件已经有了对应的价格和满意度,
        那么当前处理的就是第二个附件(即 v[q][2] 和 w[q][2])。*/
        
    }
    for (int j = 1; j < N + 1; j++) { // 遍历背包
        for (int i = 1; i < m + 1; i++) { // 遍历物品

            if (v[i][0] > j) {
                dp[i][j] = dp[i-1][j];  // 都不买
                /* 在考虑第 i 个物品时,如果不放入第 i 个物品(即选择不放)
                那么最大价值将与前 i-1 个物品、容量为 j 的情况下的最大价值相同 */

            } else {
                if (v[i][0] <= j) { 
                /* 如果主件的重量小于等于背包当前的容量 j
                则考虑将主件放入背包中,更新 dp[i][j] 的值 */

                    dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i][0]]+w[i][0]);
                    /* 更新 dp[i][j] 为当前最大值,考虑两种情况:
                    不放入第 i 个物品主件,dp[i][j] 保持不变
                    放入第 i 个物品的主件,
                    更新背包容量为 j - v[i][0] 后的最大价值,再加上该主件的价值*/
                }

                if (v[i][0]+v[i][1] <= j) { // 买主件+附件1
                /* 检查主件和附件的总重量 v[i][0] + v[i][1] 
                是否小于等于当前背包的容量 j */

                    dp[i][j] = max(dp[i][j], dp[i-1][j-v[i][0]-v[i][1]]+w[i][0]+w[i][1]);
                    /* 选择当前背包容量 j 下的最大价值,
                    要么是选择不放入当前物品的主件和附件,保持原值 dp[i][j],
                    要么是选择放入当前物品的主件和附件,更新为 
                    dp[i-1][j-v[i][0]-v[i][1]]+w[i][0]+w[i][1] */
                }

                if (v[i][0]+v[i][2] <= j) { // 买主件+附件2
                /* 检查主件和附件的总重量 v[i][0] + v[i][2] 
                是否小于等于当前背包的容量 j */

                    dp[i][j] = max(dp[i][j], dp[i-1][j-v[i][0]-v[i][2]]+w[i][0]+w[i][2]);
                    /* 选择当前背包容量 j 下的最大价值,
                    要么是选择不放入当前物品的主件和附件,保持原值 dp[i][j],
                    要么是选择放入当前物品的主件和附件,更新为 
                    dp[i-1][j-v[i][0]-v[i][2]]+w[i][0]+w[i][2] */
                }

                if (v[i][0]+v[i][1]+v[i][2] <= j) { // 买主件+附件1+附件2
                /* 检查主件和附件的总重量 v[i][0]+v[i][1]+v[i][2] 
                是否小于等于当前背包的容量 j */

                    dp[i][j] = max(dp[i][j], dp[i-1][j-v[i][0]-v[i][1]-v[i][2]]+w[i][0]+w[i][1]+w[i][2]);
                    /* 选择当前背包容量 j 下的最大价值,
                    要么是选择不放入当前物品的主件和附件,保持原值 dp[i][j],
                    要么是选择放入当前物品的主件和附件,更新为 
                    dp[i-1][j-v[i][0]-v[i][1]-v[i][2]]+w[i][0]+w[i][1]+w[i][2] */
                }
            }    
        }
    }
    cout << dp[m][N] * 10 << endl;
    /* 由于输入的背包容量 N 和物品的价格都是按 10 的倍数给出的,
    因此在代码中已经将它们除以 10 以简化计算
    这里的 * 10 是为了将最终的结果还原成以 10 为单位的满意度值 */

    return 0;
}

思路解析:

  1. 输入:

    • N:背包容量(单位是10),需要进行转换为整数
    • m:物品数量,每个物品可能有多个附件
  2. 数据存储结构:

    • v:存储每个物品的价格;v[i][0] 存储第 i 个物品主件的价格,v[i][1] 和 v[i][2] 分别存储附件1和附件2的价格
    • w:存储价格与重要度的乘积,表示满意度;w[i][0] 存储第 i 个物品主件的满意度,w[i][1] 和 w[i][2] 存储附件1和附件2的满意度
    • dp:动态规划数组,dp[i][j] 表示前 i 个物品,背包容量为 j 时能够获得的最大满意度
  3. 动态规划:

    • 初始化二维数组 dp,其中 dp[i][j] 表示在考虑前 i 个物品时,背包容量为 j 的情况下的最大满意度
    • 对于每个物品,判断是否可以放入背包中,考虑不同的组合(只放主件、放主件和附件1、放主件和附件2、放主件和两个附件)
    • 更新 dp[i][j] 为当前最大满意度,即选择不放入当前物品或放入当前物品的最佳方案
  4. 输出:

    • 输出最终的最大满意度,乘以10转换回原始单位。

代码细节:

  1. 物品与附件处理:

    • 每个物品分为主件和附件,如果是附件(q != 0),通过 q 确定它属于哪个主件;附件的价格和满意度分别存储到 v[q][1] 和 w[q][1](附件1),v[q][2] 和 w[q][2](附件2)。
  2. 动态规划转移:

    • 对于每个物品,尝试将物品主件及附件加入背包,并更新背包的最大满意度
    • 使用 max(dp[i-1][j], dp[i-1][j-v[i][0]] + w[i][0]) 来考虑放入或不放入物品主件
    • 对于附件,类似地更新 dp[i][j],分别考虑放入附件1、附件2或者同时放入主件和附件

代码的时间复杂度:

  • 由于双重循环遍历所有物品和背包容量,时间复杂度为 O(m * N),其中 m 为物品数量,N 为背包容量

0-1 背包问题是一个经典的动态规划问题

问题描述

给定一个背包,背包有一定的容量 C。同时有 n 个物品,每个物品有两个属性:重量 w[i] 和价值 v[i];每个物品只能选择一次,即要么放入背包,要么不放入背包;目标是选择一些物品放入背包,使得背包中的物品总重量不超过容量 C,并且物品的总价值最大

输入

  • n:物品的数量
  • C:背包的容量
  • w[i]:第 i 个物品的重量
  • v[i]:第 i 个物品的价值

输出

  • 背包能够容纳的最大价值

动态规划解法

为了求解该问题,通常使用动态规划(DP)方法;定义一个二维的 dp 数组,其中 dp[i][j] 表示在前 i 个物品中,容量为 j 的背包能够容纳的最大价值

状态转移方程

  • dp[i][j] = dp[i-1][j]:表示不选择第 i 个物品的情况,即价值等于不放这个物品时的最大价值
  • dp[i][j] = max(dp[i][j], dp[i-1][j-w[i]] + v[i]):表示选择第 i 个物品的情况,前提是 j 大于等于 w[i](即当前背包容量足够容纳该物品)

最终,dp[n][C] 就是我们要求的最大价值

代码实现

#include 
#include 
#include 

using namespace std;

int main() {
    int n, C;
    cin >> n >> C;  // 输入物品的数量和背包的容量
    vector w(n + 1), v(n + 1);  // w[i] 是第i个物品的重量,v[i] 是第i个物品的价值

    // 输入每个物品的重量和价值
    for (int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
    }

    // dp[i][j] 表示前i个物品,背包容量为j时的最大价值
    vector> dp(n + 1, vector(C + 1, 0));

    // 动态规划填充 dp 数组
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= C; j++) {
            // 不选第i个物品
            dp[i][j] = dp[i - 1][j];
            // 如果选择第i个物品,且当前背包容量 >= 物品重量
            if (j >= w[i]) {
                dp[i][j] = max(dp[i][j], dp[i - 1][j - w[i]] + v[i]);
            }
        }
    }

    // 输出最大价值
    cout << dp[n][C] << endl;
    return 0;
}

二维数组是数组的一种扩展,它包含多个一维数组;可以将其看作一个表格,包含行和列;二维数组的元素可以通过行和列的索引来访问

二维数组的定义

在 C++ 中,定义一个二维数组的方式是:

type array_name[rows][cols];
  • type:数组中元素的类型(如 intfloatchar 等)
  • rows:二维数组的行数
  • cols:二维数组的列数

访问二维数组元素

二维数组的元素通过 [row][col] 的方式来访问,其中 row 是行索引,col 是列索引,注意数组的索引从 0 开始

arr[0][0] = 1;  // 第一行第一列的元素赋值为 1
arr[1][2] = 5;  // 第二行第三列的元素赋值为 5

 初始化二维数组

可以在定义数组时直接初始化:

静态初始化

int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

上述代码定义了一个 3x4 的数组,并为每个元素赋了初值

部分初始化

如果没有初始化所有的元素,其他未初始化的元素会默认初始化为 0

int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6}
};

动态分配二维数组

可以使用动态内存分配来创建二维数组,这种方法在运行时动态地为数组分配内存

int **arr;
int rows = 3, cols = 4;

// 动态分配二维数组
arr = new int*[rows]; // 为行分配内存
for (int i = 0; i < rows; i++) {
    arr[i] = new int[cols]; // 为每一行分配内存
}

// 使用二维数组
arr[0][0] = 1;
arr[1][2] = 5;
arr[2][3] = 10;

// 释放内存
for (int i = 0; i < rows; i++) {
    delete[] arr[i]; // 释放每一行的内存
}
delete[] arr; // 释放二维数组的内存

遍历二维数组

可以使用嵌套循环来遍历二维数组;外层循环用于遍历行,内层循环用于遍历列

int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        cout << arr[i][j] << " ";  // 输出每个元素
    }
    cout << endl;  // 换行
}

你可能感兴趣的:(【华为od刷题(C++)】HJ16 购物单(动态规划、0-1 背包问题、二维数组))