day35 卡码网46.动态规划:01背包理论基础 力扣416. 分割等和子集

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

01 背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

动规五部曲:

1.i表示物品位置,j表示背包容量,dp[i][j]表示从0到i的物品,背包容量到校为j的所能装的价值总和。

2.初始化,对于dp[i][[0]初始化为0(因为背包容量为0,则必然无法装东西)

对于dp[0][j],如果j<weight[0]初始化为0,如果j>=weight[0],就是初始化为value[0].

3.递推公式,dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]).

4.遍历顺序,从前往后(从后往前也可),优先 遍历背包还是遍历物品也都行.

5.略。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
    int type ; int lunggage;
    cin >> type >> lunggage;
    vector<int> materialSpace(type);
    vector<int> materialValue(type);
   for(int i = 0; i < type; ++i) {
        cin >> materialSpace[i];
    }
    for(int j = 0; j < type; ++j) {
        cin >> materialValue[j];
    }
    vector<vector<int>> dp(type,vector<int> (lunggage+1,0)); 
    for(int j = 0;j<=lunggage;j++)
    {
        if(materialSpace[0]<=j)
        dp[0][j] = materialValue[0];
    }
    for(int i = 1;i<type;i++)
    {
        for(int j = 0;j<=lunggage;j++)
        {
            if(j<materialSpace[i])dp[i][j] = dp[i-1][j];
            else
            dp[i][j] = max(dp[i-1][j],dp[i-1][j-materialSpace[i]] +materialValue[i]);
        }
    }
    cout << dp[type - 1][lunggage] << endl;
    return 0;
}

有些乱了。

然后还有一种滚动写法:

1.dp数组定义 j表示容量,dp[j]表示在j大小容量所能容纳的最大价值。

2.初始化,全部初始化为0,即不需要处理即可。

3.递推公式: dp[j] = max(dp[j],dp[j-weight[i]] +value[i]); 要么不采用这个材料,要么采用这个材料,背包容量数量降低。

4.递推顺序:倒序(防止同一物品被连续放入多次)

先遍历物品嵌套遍历背包容量。

因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
    int m ; int n;
    cin >> m >> n;
    vector<int> weight(m);
    vector<int> value(m);
   for(int i = 0; i < m; ++i) {
        cin >> weight[i];
    }
    for(int j = 0; j < m; ++j) {
        cin >> value[j];
    }
    vector<int> dp(n+1,0); 
    for(int i = 0;i<m;i++)
    {
        for(int j = n;j>=weight[i];j--)
        {
            dp[j] = max(dp[j],dp[j-weight[i]] +value[i]);
        }
    }
    cout << dp[n] << endl;
    return 0;
}

分割等和子集

给你一个 只包含正整数非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

复制代码
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

复制代码
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

dp[i]:

01背包中,dp[j] 表示: 容量(所能装的重量)为j的背包,所背的物品价值最大可以为dp[j]。

如果背包所载重量为target, dp[target]就是装满 背包之后的总价值,因为 本题中每一个元素的数值既是重量,也是价值,所以,当 dp[target] == target 的时候,背包就装满了。

初始化为0 即可。

使用滚动数组,顺序与递推公式基本与上题一致。

cpp 复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        vector<int> dp (20001,0);
        int sum = 0;
        for(int i = 0 ;i<nums.size();i++)
        {
            sum += nums[i];
        }
        if(sum%2 == 1) return false;
        int target = sum/2;
        for(int i = 0;i< nums.size();i++)
        {
            for(int j = target;j>=nums[i];j--)
            {
                dp[j] = max(dp[j],dp[j-nums[i]]+nums[i]);
            }
        }
        if(dp[target]==target)return true;
        return false;
    }
};