0
点赞
收藏
分享

微信扫一扫

自动化运维:Ansible、Puppet、Chef工具对比与实战

江南北 2024-08-28 阅读 20

目录

一、做题心得

二、完全背包模板

模板题:AcWing 3. 完全背包问题

题目链接

模板1:最大价值问题

模板2:组合排列数问题

组合数

排列数 

三、题目与题解

题目一:518. 零钱兑换 II

题目链接

题解:动态规划--完全背包

题目二:377. 组合总和 Ⅳ

题目链接

题解:动态规划--完全背包

题目三:57. 爬楼梯(卡码网)

题目链接

题解:动态规划--完全背包

三、小结


一、做题心得

今天是代码随想录打卡的第37天,来到了动态规划章节的完全背包问题。完全背包问题相较于前边打卡的01背包问题,由一个物品只能使用一次,变成了一个物品可以使用多次。那么代码上自然也会存在相应改动。不过由于都属于背包问题的范畴,思路上讲是一致的。因此,有了前边打卡的基础,今天的内容就比较简单了。

话不多说,直接开始今天的内容。

二、完全背包模板

模板题:AcWing 3. 完全背包问题

题目链接

3. 完全背包问题 - AcWing题库

模板1:最大价值问题
#include<bits/stdc++.h>
using namespace std;

const int N = 1005;
int n, m;
int v[N];
int w[N];
int dp[N] = {0};        //也可以vector数组

signed main() {
    cin>>n>>m;
    for (int i = 0; i < n; i++) {
        cin>>v[i]>>w[i];
    }
    for (int i = 0; i < n; i++) {               //遍历物品
        for (int j = 0; j <= m; j++) {              //注意:正序遍历背包(从0到最大容量)--表示物品可以重复使用--注意与01背包区别
            if (j >= v[i]) {             //当前背包能装下该物品时(注意是大于等于)
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
            }
        }
    }
    cout<<dp[m];
    return 0;
}

其实完全背包和01背包问题的主要区别就在于:在遍历背包上,01背包采用逆序遍历,而完全背包采用顺序(正序)遍历--这是因为逆序遍历确定了每个物品只能选一次,而顺序遍历时每个物品可以选择多次。还有一个区别就是01背包只能先遍历物品再遍历背包,而完全背包两种顺序都可以。

还有一道模板题和这个一致:52. 携带研究材料(第七期模拟笔试) (kamacoder.com)

附一份代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 10005;
int n, m;
int v[N];
int w[N];
int dp[N] = {0};

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

当然,这个模板是为了解决完全背包问题对于求得最大价值这一类问题,后边还会提到解决求得方案数(组合数或者排列数)这一类完全背包问题。

模板2:组合排列数问题

组合数:同一组合,不同顺序,一种情况

排列数:同一组合,不同顺序,不同情况

组合数

先遍历物品,再遍历背包

for (int i = 0; i < n; i++) {          //组合数--先遍历物品,再遍历背包
            for (int j = 0; j <= m; j++) {
                if (j >= v[i]) {            //注意:>=
                    dp[j] += dp[j - v[i]]; 
                }
            }
        }
排列数 

先遍历背包,再遍历物品

for (int i = 0; i <= m; i++) {          //排列数--先遍历背包,再遍历物品
            for (int j = 0; j < n; j++) {
                if (i >= v[j]) {            //注意:>=
                    dp[i] += dp[i - v[j]]; 
                }
            }
        }

三、题目与题解

题目一:518. 零钱兑换 II

题目链接

518. 零钱兑换 II - 力扣(LeetCode)

题解:动态规划--完全背包

这道题一看到每一种面额的硬币有无限个,就可以联想到这是一个完全背包的组合数问题

题目要求得出可以凑成总金额的硬币组合数,其实就是刚刚模板2中的组合数问题。(不讲求顺序)在这里物品就是硬币,dp[j]表示凑成金额总数为j的组合数。

这里需要注意的就是初始化 dp[0] = 1。

代码如下:

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        /*     完全背包问题--一维dp    */
        vector<int> dp(5001, 0);            //dp[j]:凑成金额总数为j的组合数
        dp[0] = 1;          //注意:初始化dp[0] = 1(后续中,若j == coins[i],选择当前硬币,就出现dp[0],若dp[0] == 0,组合数就没有增加)
        for (int i = 0; i < coins.size(); i++) {          //组合数--先遍历物品,再遍历背包
            for (int j = 0; j <= amount; j++) {
                if (j >= coins[i]) {            //注意:>=
                    dp[j] += dp[j - coins[i]]; 
                }
            }
        }
        return dp[amount];
    }
};

题目二:377. 组合总和 Ⅳ

题目链接

377. 组合总和 Ⅳ - 力扣(LeetCode)

题解:动态规划--完全背包

这道题就是一个典型的完全背包的排列数问题,即每个组合的情况是需要讲求顺序的。

在这里需要注意的就是需要防溢出:为了防止dp[i] + dp[i - nums[j]] 过大溢出,我们需要保证 dp[i - nums[j]] < INT_MAX - dp[i]。

其余的思路上就跟上一题一致,只是排列数问题需要先遍历背包,再遍历物品。

代码如下:

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(1005, 0);
        dp[0] = 1;      //注意求方案数的背包问题:初始化dp[0] = 1
        for (int i = 0; i <= target; i++) {         //排列数--先遍历背包再遍历物品
            for (int j = 0; j < nums.size(); j++) {
                if (i >= nums[j] && dp[i - nums[j]] < INT_MAX - dp[i]) {        //题目数据保证答案符合 32 位整数范围:dp[i - nums[j]] < INT_MAX - dp[i]在这里用作防溢出
                    dp[i] += dp[i - nums[j]];
                }
            }
        }
        return dp[target];
    }
};

题目三:57. 爬楼梯(卡码网)

题目链接

57. 爬楼梯(第八期模拟笔试) (kamacoder.com)

题解:动态规划--完全背包

这题也比较简单,很容易想到完全背包的做法--完全背包的排列数问题(即需要讲求顺序)。

物品在这就是指你能爬台阶的步数(1--m相当于各个物品的体积),背包就是台阶。dp[i]表示台阶数为 i 时爬到楼顶的方案数(其实就是排列数)。

理清题意之后,这里也是直接套用模板2的排列数问题。

代码如下:

#include<bits/stdc++.h>
using namespace std;

int n, m;
vector<int> dp(35, 0);

signed main() {
    cin>>n>>m;
    dp[0] = 1;
    for(int i = 0; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (i >= j) {
                dp[i] += dp[i - j];
            }
        }
    }
    cout<<dp[n];
    return 0;
}

三、小结

有了前边01背包的打卡练习,今天完全背包的问题也变得容易理解。理解了模板的由来之后,今天的打卡基本上就是模板的套用。最后,今天的打卡到此结束,后边将会继续加油!

举报

相关推荐

0 条评论