背包总结——0-1背包及完全背包问题总结及代码模板

本文总结了背包问题中的0-1背包和完全背包,讲解了各自的特性、状态转移方程,并提供了代码模板。0-1背包问题中,物品只能使用一次,而完全背包允许无限次使用同一物品。文章通过实例展示了如何根据问题要求选择合适的背包类型,以及如何优化代码实现解题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背包总结

背包问题通常是多种物品有多个属性,且已知条件为某属性被受限,求另一属性的最大/最小/等于/存在不存在。以0-1背包为例解释:n个物品具有的属性为重量和价值,其中总重量C将重量的属性限制住,求最大价值,即求另一属性的特征。

针对背包问题:

1、先判断属于0-1背包还是完全背包

2、看是求最大值/最小值/等值/是否存在/排列/组合(排列/组合问题通常出现在完全背包中)。

确定了背包类型及要求的问题后,即可下手做题。

1、0-1背包

0-1背包是指物品只能使用一次,故通常其状态方程为:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w i ] + v i ) dp[i][j] = max(dp[i-1][j],dp[i-1][j-wi]+vi) dp[i][j]=max(dp[i1][j],dp[i1][jwi]+vi)
式中:dp[i][j]:将前i个物品放入容量为j的背包中可得的最大价值。dp[i-1][j]是指不取第i个物品所的最大价值
dp[i-1][j-wi]+vi则为取i个物品时所得的最大价值。

同样,若求最小值则为**dp[i][j] = min(dp[i-1][j],dp[i-1][j-wi]+vi)。**

若求等值个数则为
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − w i ] ) dp[i][j] =dp[i-1][j]+dp[i-1][j-wi]) dp[i][j]=dp[i1][j]+dp[i1][jwi])
式中:dp[i][j]:前i个元素可组成和为j的个数。dp[i-1][j]是指不取第i个物时,前i-1个物品可组成和为j的个数
dp[i-1][j-wi]+vi则为取i个物品时可组成和为j的个数。

求是否存在的题,状态转移为:
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] ∣ ∣ d p [ i − 1 ] [ j − w i ] ) dp[i][j] = dp[i-1][j] || dp[i-1][j-wi]) dp[i][j]=dp[i1][j]dp[i1][jwi])
式中:dp[i][j]:前i个元素是否可组成和为j的个数。dp[i-1][j]是指不取第i个物时,前i-1个物品是否可组成和为j的个数
dp[i-1][j-wi]+vi则为取i个物品时是否可组成和为j的个数。

在了解完0-1基本概念及相关问题后,可进一步编程实现。建议先看下文中0-1背包问题,详细介绍了背包问题的思路及优化方法。。。现以一道0-1背包题解释优化后的代码(套路,理解了即可解决许多问题)。

**输入:**第一行输入两个整数N和X(N代表物品个数、X代表背包容量)

​ 第二行输入第1个物品的价格A1和所占容量B1

​ 第N+1行代表第第1个物品的价格A2和所占容量B2

**输出:**在背包容量X内所的最大价格

分析:首先,该题符合0-1背包。其次要求计算最大价格。那么套用之前的状态转移方程可得代码:

#include "pch.h"
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {

	int N, X;
	cin >> N >> X;

	vector<int> A(N, 0);
	vector<int> B(N, 0);
	for (int i = 0; i < N; i++)
	{
		cin >> A[i] >> B[i];
	}

	// 1、优化后计算只需一维vector即可
	vector<int> dp(X + 1, 0);

	for (int i = 0; i < N; i++)
	{
		// 2、根据状态方程知道,每次获得值时,其实只需要上一层的值及该层之前的值,故只要逆序获得值,即不会覆盖上一层的值
		for (int j = X; j >= B[i]; j--)
		{
			dp[j] = max(dp[j], dp[j - B[i]]+A[i]);   // 3、dp[j]为不取第i个值时,所得最大价值。另一个为取该值时(j-A[i])容量所得最大价值
		}
	}
	cout << dp[X];
}

另:leetcode416为判断是否存在的问题,可自行思考,现附上代码:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int len = nums.size();

        // 求总数
        int sum = 0; 
        for(int i=0;i<len;i++)
        {
            sum += nums[i];
        }
        // 若为奇数,直接返回0
        if(sum % 2) return 0;

        int target = sum >> 1;

        vector<bool> dp(target+1,0);
        dp[0] = true;   // 初始值可先忽略,然后在分析的时候就可知应为多少

        for(int i=0;i<len;i++)
        {
            for(int j=target;j>=nums[i];j--)  // 注意范围
            {
                dp[j] = dp[j] || dp[j-nums[i]];
            }
        }

        return dp[target];

    }
};
2、完全背包

完全背包与0-1的差别在于,完全背包中每个物品的取值无限制,故对于dp[i][j],当取i时,则dp[i][j]dp[i][j-wi]+vi(由于i有无限个,故取i时,则仍位于i处,而不是i-1处。这处即为二者的区别)。。由于状态转义方程有些许差别,其在代码编写过程中,也不一样。。以leetcode322为例:

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int len = coins.size();

        // vector<long> dp(amount+1,INT_MAX);
        vector<long> dp(amount+1,amount+1);  // 1、若选用INT_MAX过于浪费空间(因为硬币不可能为0,至少为1,故最多也不超过amount+1)

        dp[0] = 0;  // 2、初始值在分析问题时确定

        for(int i=0;i<len;i++)
        {
            for(int j = coins[i];j<=amount;j++)   // 3、注:无限背包需要本层的之前元素,故直接从头覆盖即可(不懂得见状态方程)
            {
                dp[j] = min(dp[j],dp[j-coins[i]]+1);  // 每次取i都得加一个硬币,这也是为啥dp[0] = 0
            }
        }

        return dp[amount] >= amount+1 ? -1:dp[amount];
    }
};

另外,还需注意的是,完全背包在求得到目标的组合个数时,通常会有排序和不排序两种方法。具体编程实现也有区别,见leetcode518与377.

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        
        int len = coins.size();
        vector<int> dp(+1,0);

        dp[0] = 1;
        for(int i=0;i<len;i++)   // 与排列顺序无关,故按之前套路来即可
        {
            for(int j = coins[i];j<=amount;j++)
            {
                dp[j] = dp[j]+ dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
};
class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        int len = nums.size();

        vector<long> dp(target+1,0);

        dp[0] = 1;   // 1、状态转移方程求得是组合,与硬币数不一样,故初始化应为1
        for(int i=1;i<= target;i++)   // 求排列的问题,则应该按此种模板进行
        {
            for(int j = 0;j<len;j++)
            {
                if(nums[j] <= i)
                    dp[i] = (dp[i] + dp[i-nums[j]])%INT_MAX;    // dp[i-nums[j]] 意味着,目标值为i-nums[i]对应点组合数
            }
        }
        return dp[target];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值