代码随想录刷题Day42 | 动态规划:01背包理论基础、动态规划:01背包理论基础(滚动数组)、416. 分割等和子集

代码随想录刷题Day42 | 动态规划:01背包理论基础、动态规划:01背包理论基础(滚动数组)、416. 分割等和子集

今日任务

动态规划:01背包理论基础

动态规划:01背包理论基础(滚动数组)

416. 分割等和子集

动态规划:01背包理论基础

题目链接(acwing):01背包问题

主要思路

  • 每个物品分为放入和不放入两种状态
  • dp数组 d[i][j] 表示在第 [0, i] 个物品中选择几个物品放进容量为 j 的背包时,背包中物品最大价值
  • 有两种情况
    • 第一种是当第 i 个物品的体积大于当前背包容量时(Volume[i] > j ),此时必定无法向背包放入第 i 个物品:dp[i][j] = dp[i - 1][j]
    • 第二种是当 volume[i] <= j,可以向背包放入第 i 个物品,此时也有两种情况
      • 可以继续选择不放入第 i 个物品,此时 dp[i][j] = dp[i - 1][j]
      • 可以选择放入第 i 个物品,此时 dp[i][j] = dp[i - 1][j - Volume[i]] + Wealth[i]
      • 由于我们要使背包中物品价值最大,所以最终递推公式为:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - Volume[i]])

最终代码

#include <iostream>
using namespace std;

const int N = 1005;
int Volume[N], Wealth[N];
int dp[N][N]; // dp[i][j] 表示从第[0, i]个物品中取物品,其体积总和不超过j时最大价值
// dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - Volume[i]] + wealth[i]) 不放第i个物品和放第i个物品

int main() {
    int n, v;
    cin >> n >> v;
    for (int i = 0; i < n; ++i) {
        cin >> Volume[i] >> Wealth[i];
    }
    // 正序遍历
    // 先初始化dp数组
    // 当只从第0个物品开始挑选时有两种情况
    // 1.容量j小于物体体积Volume[0], 那么背包不能放东西,此时dp[0][j] = 0;
    for (int j = 0; j < Volume[0]; ++j) dp[0][j] = 0;
    // 2.容量j大于等于物体体积Volume[0],此时可以放入物体0,所以dp[0][j] = Wealth[0]
    for (int j = Volume[0]; j <= v; ++j) dp[0][j] = Wealth[0];
    
    for (int i = 1; i < n; ++i) { // 遍历物品
        for (int j = 0; j <= v; ++j) { // 遍历背包容量
            if (j < Volume[i]) dp[i][j] = dp[i - 1][j];
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - Volume[i]] + Wealth[i]);
        }
    }
    cout << dp[n - 1][v];
    return 0;
}

动态规划:01背包理论基础(滚动数组)

题目链接同上一题

主要思想

  • 根据上一题的推导,我们将第 i - 1 层的值拷贝到第 i 层时,依旧可以可行:dp[i][j] = max(dp[i][j], dp[i][j - Volume[i]] + Wealth[i])
  • 所以我们可以删掉 i 这个维度,直接用 dp[j] 来表示背包容量为 j 时,背包所能容纳的物品的最大价值
  • 递推公式同样分为两种情况:
    • 一种不放物品 i,此时取自己原本的值:dp[j] = dp[j]
    • 一种放物品 i,此时不取自己的值:dp[j] = dp[j - Volume[i]] + Wealth[i]
    • 最后取最大值:dp[j] = max(dp[j], dp[j - Volume[i]] + Wealth[i])
  • 注意:倒序遍历是为了保证物品i只被放入一次!
    • 如果是正序遍历,由于每次都要用到上一层(i - 1层)的 dp[j]dp[i - 1][j]) 的值,每次是从 左往右逐步扩大取物品区间 [0, i] ,这就可能导致上一层的 **dp[j]dp[i - 1][j])**已经更新为这一层的 dp[j]dp[i][j];而我们在计算第 i 层时,仍然在取前面的 dp[j]dp[i][j])进行计算时,原本是要取用 dp[i - 1][j]dp[j] 值,但是用的是更新后的 d[i][j] 值,会导致有的物品多次放入背包
    • 如果逆序遍历,由于先更新右边的值,再往左走时,左边用的是更左边的 dp[j] 值来进行更新,不会使用到已经更新的值,所以不会出现重复
image-20230919200000017
#include <iostream>
using namespace std;

const int N = 1005;
int dp[N], Volume[N], Wealth[N];

int main() {
    int n, v;
    cin >> n >> v;
    for (int i = 0; i < n; ++i) cin >> Volume[i] >> Wealth[i];
    for (int i = 0; i < n; ++i) {
        for (int j = v; j >= Volume[i]; --j) {
            dp[j] = max(dp[j], dp[j - Volume[i]] + Wealth[i]);
        }
    }
    cout << dp[v];
    return 0;
}

416. 分割等和子集

题目链接416. 分割等和子集

主要思想

  • 背包的体积为 sum / 2
  • 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
  • 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素是不可重复放入。
  • dp[sum / 2] == sum / 2 时,说明背包正好放满,说明该集合可以划分为两个和相等的子集
class Solution {
public:
// 背包的体积为sum / 2
// 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
// 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
// 背包中每一个元素是不可重复放入。
// 当dp[sum / 2] == sum / 2时,说明背包正好放满,说明该集合可以划分为两个和相等的子集
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for (auto x : nums) sum += x;
        if (sum % 2 != 0) return false;
        sum /= 2;
        vector<int> dp(sum + 1, 0); // dp[j] 表示容量为j时装入背包的最大数值和
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = sum; j >= nums[i]; --j) {
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        return dp[sum] == sum;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值