动态规划——完全背包问题

目录

一、完全背包

二、零钱兑换

三、零钱兑换II

四、完全平方数


一、完全背包

完全背包可以描述为:有n件物品和一个最多能背体积为 v 的背包。第 i 件物品的体积是 v[i],得到的价值是value[i] 。每件物品有无限多个,求解将哪些物品装入背包里物品价值总和最大。

在完全背包问题中,对于每件物品,有多种状态:不选,选一个,选两个......

题目

完全背包

题目解析

第一问的所有情况就是上图红色方框的内容, 第二问的所有情况就是上图蓝色方框的内容。

解决第一问

第一步:确定状态表示

dp[i][j]:表示从前 i 个物品中挑选,总体积不超过 j 的所有选法中,能挑选出来的物品价值最大值。

第二步:推出状态转移方程

前面我们已经说明了,完全背包中的每件物品的数量有无限个,所以对于每件物品来说,有选或者选1个,选2个...... 多种状态。根据最后一步的状况,分情况讨论,即要么不选择 i 号物品,要么选 1个 i 号物品,要么选 2个 i 号物品,要么选 3个 i 号物品......

选一个 i 物品,这时就有一个v[i]的体积了,然后仅需去 1 ~ i - 1 区间选不超过 j - v[i] 的最大价值,然后再加上 i 的价值。

选两个 i 物品,这时就有两个v[i]的体积了,然后仅需去 1 ~ i - 1 区间选不超过 j - 2*v[i] 的最大价值,然后再加上两个 i 的价值。

选n个 i 物品,这时就有n个v[i]的体积了,然后仅需去 1 ~ i - 1 区间选不超过 j - n*v[i] 的最大价值,然后再加上n个 i 的价值。

第三步:初始化dp表

第一列不需要初始化。

第一行表示没有物品,当 j 为0的时候,要凑成不超过 j,只要不选就行了,最大价值是0。如果j为1、2、3......但是没有物品可选,所以最大价值也是0。

解决第二问

第一步:确定状态表示

dp[i][j]:表示从前 i 个物品中挑选,总体积恰好为 j 的所有选法中,能挑选出来的物品价值最大值。

第二步:推出状态转移方程

状态转移方程的推导方法和第一问相同,只是有一些细节问题需要注意。

题目要求选出的物品中,总体积要恰好为 j ,那么就有可能怎么选也凑不出物品的总体积恰好为 j 的情况,那么我们就不能使用这种情况,可以用 dp[i][j] == -1 来表示凑不出物品的总体积恰好为 j 的情况,即这种情况不存在。

第三步:初始化dp表

第一列不需要初始化。

第一行表示没有物品,当 j 为0的时候,要凑成不超过 j,只要不选就行了,最大价值是0。如果j为1、2、3......因为没有物品可选,所以这些情况都不存在,值为-1。

解题代码:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() 
{
    int n, V;
    cin >> n >> V;
    vector<int> v(n);
    vector<int> w(n);
    for(int i = 0; i < n; i++)
        cin >> v[i] >> w[i];

    vector<vector<int>> dp1(n+1, vector<int>(V+1));
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= V; j++)
        {
            dp1[i][j] = dp1[i-1][j];
            if(j >= v[i-1])
            {
                int ret = max(dp1[i-1][j], dp1[i][j-v[i-1]] + w[i-1]);
                dp1[i][j] = max(dp1[i][j], ret);
            }
        }
    }

    vector<vector<int>> dp2(n+1, vector<int>(V+1));
    for(int i = 1; i <= V; i++)
        dp2[0][i] = -1; // 用-1来表示该情况不存在
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= V; j++)
        {
            dp2[i][j] = dp2[i-1][j];
            if(j >= v[i-1] && dp2[i][j-v[i-1]] != -1)
            {
                int ret = max(dp2[i-1][j], dp2[i][j-v[i-1]] + w[i-1]);
                dp2[i][j] = max(dp2[i][j], ret);
            }
        }
    }

    cout << dp1[n][V] << endl;
    cout << (dp2[n][V] == -1 ? 0 : dp2[n][V]) << endl;
    return 0;
}

二、零钱兑换

零钱兑换

第一步:确定状态表示

dp[i][j]:表示从前 i 个硬币中挑选,使其总和恰好等于j,所有选法中,最少的硬币个数。

第二步:推出状态转移方程

第三步:初始化dp表

第一列不用初始化。第一行表示没有硬币,当 j 为0表示硬币总和为0,不选就行了。

解题代码:

cpp 复制代码
class Solution 
{
public:
    int coinChange(vector<int>& coins, int amount) 
    {
        int m = coins.size();
        vector<vector<int>> dp(m+1, vector<int>(amount+1));
        for(int i = 1; i <= amount; i++)
            dp[0][i] = 0x3f3f3f3f;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 0; j <= amount; j++)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= coins[i-1])
                    dp[i][j] = min(dp[i][j], dp[i][j-coins[i-1]]+1);
            }
        }
        return dp[m][amount] == 0x3f3f3f3f ? -1 : dp[m][amount];
    }
};

三、零钱兑换II

零钱兑换II

第一步:确定状态表示

dp[i][j]:表示从前 i 个硬币中挑选,使其总和恰好等于j,一共有多少种选法。

第二步:推出状态转移方程

第三步:初始化dp表

第一列不用初始化。

第一行表示没有硬币,当 j = 0时表示总和为0,不选就行了,这是一种选法。当j = 1、2、3...,因为没有硬币,所以根本凑不出总和等于j的选法。直接都给0就行了。

解题代码:

cpp 复制代码
class Solution 
{
public:
    int change(int amount, vector<int>& coins) 
    {
        int m = coins.size();
        vector<vector<double>> dp(m+1, vector<double>(amount+1));
        dp[0][0] = 1;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 0; j <= amount; j++)
            {
                dp[i][j] += dp[i-1][j];
                if(j >= coins[i-1])
                    dp[i][j] += dp[i][j-coins[i-1]];
            }
        }
        return dp[m][amount];
    }
};

四、完全平方数

完全平方数

第一步:确定状态表示

dp[i][j]:表示从前 i 个完全平方数种挑选,使其总和恰好等于 j,所有选法中,平方数的最少数量。

第二步:推出状态转移方程

第三步:初始化dp表

第一列不用初始化。

第一行表示完全平方数为0,当 j = 0表示总和为0,不选就行了,最少数量为0。当 j = 1、2...时完全平方数为0,根本凑不出和为 j,然后我们填dp[i][j]要最小值,为了不让这些位置得值影响填表,因此可以给0x3f3f3f3f。

解题代码:

cpp 复制代码
class Solution 
{
public:
    int numSquares(int n) 
    {
        int m = sqrt(n);
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        for(int i = 1; i <= n; i++)
            dp[0][i] = 0x3f3f3f3f;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 0; j <= n; j++)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= i*i)
                    dp[i][j] = min(dp[i][j], dp[i][j-i*i]+1);
            }
        }
        return dp[m][n];
    }
};
相关推荐
shinelord明1 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
დ旧言~8 分钟前
专题八:背包问题
算法·leetcode·动态规划·推荐算法
_WndProc25 分钟前
C++ 日志输出
开发语言·c++·算法
努力学习编程的伍大侠38 分钟前
基础排序算法
数据结构·c++·算法
林辞忧40 分钟前
动态规划<四> 回文串问题(含对应LeetcodeOJ题)
动态规划
XiaoLeisj1 小时前
【递归,搜索与回溯算法 & 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(二)
数据结构·算法·leetcode·决策树·深度优先·剪枝
Jasmine_llq1 小时前
《 火星人 》
算法·青少年编程·c#
闻缺陷则喜何志丹2 小时前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
Lenyiin2 小时前
01.02、判定是否互为字符重排
算法·leetcode