【算法】动态规划背包问题

​​​​​​​​​​​​​​​​​​在这里插入图片描述

一、01背包问题

题目描述

有N件物品和一个容量是V的背包。每件物品只能使用一次。
第i件物品的体积是viv_ivi,价值是wiw_iwi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wivi,wivi,wi,用空格隔开,分别表示第 i件物品的体积和价值。

输入样例
4 5
1 2
2 4
3 4
4 5

输出样例
8

解题步骤

1 状态表示:确定问题的状态

状态表示dp[i][j]dp[i][j]dp[i][j],表示考虑装前i个物品,当背包容量为j时,背包内所有物品总价值的最大值。
题目所求的背包价值最大值为dp[N][V]dp[N][V]dp[N][V]

2 状态计算

当第i个物品的体积viv_ivi大于j时,背包装不下第i个物品。
dp[i][j]=dp[i−1][j]dp[i][j] = dp[i-1][j]dp[i][j]=dp[i1][j]

当第i个物品的体积viv_ivi小于等于j时,我们可以装第i个物品,也可以不装第i个物品。
若装第i个物品,该状态由装前i-1个物品的状态推广而来。当第i个物品装入背包时,背包容量减少v[i],背包价值增加w[i],所以状态方程为
dp[i][j]=max(dp[i−1][j],dp[i−1][j−v[i]]+w[i])dp[i][j] = max(dp[i-1][j], dp[i-1][j - v[i]] + w[i])dp[i][j]=max(dp[i1][j],dp[i1][jv[i]]+w[i])

3 初始化
当i=0时,无论背包容量为何值,背包内没有物品,总价值为0。

#include <iostream>

using namespace std;

const int N = 1010;

int v[N];
int w[N];
int dp[N][N];

int main()
{
    int n, m;
    cin >> n >> m;
    
    for(int i = 1; i <= n; i ++)
        scanf("%d %d", &v[i], &w[i]);
        
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= m; j ++)
        {
        
            if(j - v[i] >= 0)
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
            else
                dp[i][j] = dp[i - 1][j];
        }
    }
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= m; j ++)
        {
            printf("%d ", dp[i][j]);
        }
        printf("\n");
    }
    cout << dp[n][m];
    
    return 0;
}

DP优化

观察二维的动态规划转移方程,我们可以发现两个关键点:

  • f[i][...]只依赖于f[i−1][...]:也就是说,当前第i层的状态只依赖于第i−1层的状态,而与更早的层(如i−2,i−3等)无关。这意味着我们不需要保存所有i的历史信息,只需要保存上一层的信息即可。f[i][...] 只依赖于 f[i - 1][...]:也就是说,当前第 i 层的状态只依赖于第 i-1 层的状态,而与更早的层(如 i-2, i-3 等)无关。这意味着我们不需要保存所有 i 的历史信息,只需要保存上一层的信息即可。f[i][...]只依赖于f[i1][...]:也就是说,当前第i层的状态只依赖于第i1层的状态,而与更早的层(如i2,i3等)无关。这意味着我们不需要保存所有i的历史信息,只需要保存上一层的信息即可。
  • f[i][j]依赖于f[i−1][j]和f[i−1][j−v[i]]:具体来说,f[i][j]依赖于上一层的j和j−v[i]位置的值。这两个位置都在当前j的左侧(即j−v[i]<=j)f[i][j] 依赖于 f[i - 1][j] 和 f[i - 1][j - v[i]]:具体来说,f[i][j] 依赖于上一层的 j 和 j - v[i] 位置的值。这两个位置都在当前 j 的左侧(即 j - v[i] <= j)f[i][j]依赖于f[i1][j]f[i1][jv[i]]:具体来说,f[i][j]依赖于上一层的jjv[i]位置的值。这两个位置都在当前j的左侧(即jv[i]<=j

基于以上两点,我们可以将二维数组优化为一维数组:

状态表示:用 f[j] 表示总体积不超过 j 时的最大价值。
状态计算:在更新 f[j] 时,我们需要的是上一层的 f[j] 和 f[j - v[i]] 的值。

为什么需要从后往前更新?

关键在于如何保证在更新 f[j] 时,f[j - v[i]] 仍然是上一层的值(即未被当前层的更新所覆盖)。

如果从前往后更新:假设我们正在处理第 i 个物品,j 从小到大遍历。当更新 f[j] 时,f[j - v[i]] 可能已经被当前层的更新覆盖(即已经用第 i 个物品的信息更新过),这样就会导致重复计算(即同一个物品被多次选取),这与01背包问题中每个物品只能选一次的条件矛盾。

而从后往前更新可以避免这个问题:

在j 从大到小遍历(从 V 到 v[i])的过程中,在更新 f[j] 时,f[j - v[i]] 还没有被当前层的更新覆盖,仍然是上一层的值,因此可以正确反映“不选当前物品”和“选当前物品”的两种情况。

#include <iostream>
#include <algorithm>
 
using namespace std;
 
const int N = 1010;
 
int n, m;
int v[N], w[N];
int f[N];
 
int main()
{
    cin >> n >> m;
 
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
 
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= v[i]; j -- )
            f[j] = max(f[j], f[j - v[i]] + w[i]);
 
    cout << f[m] << endl;
 
    return 0;
}

二、分组背包问题

题目描述

有 N组物品和一个容量是 V的背包。

每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是 vijv_{ij}vij,价值是 wijw_{ij}wij,其中 i 是组号,j是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 N组数据:

每组数据第一行有一个整数 SiS_iSi,表示第 i 个物品组的物品数量;每组数据接下来有 SiS_iSi 行,每行有两个整数 vijv_{ij}vij,wijw_{ij}wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;

输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5

输出样例:
8


解题步骤

分组背包问题是01背包问题的扩展,其特点是:

  • 物品被分为若干组,每组内有若干物品。
  • 每组中只能选一个物品或不选(即每组最多选一个物品)。

1 状态表示:确定问题的状态

状态表示dp[i][j]dp[i][j]dp[i][j],表示考虑装前i组物品且每组物品最多只装一个时,当背包容量为j时,背包内所有物品总价值的最大值。
题目所求的背包价值最大值为dp[N][V]dp[N][V]dp[N][V]

2 状态计算

对于第 i 组物品,我们有以下选择:

  1. 不选该组的任何物品:状态直接从 dp[i-1][j] 转移而来。

  2. 选该组的某个物品 k(假设该组有 S 个物品):该物品的体积为 v[i][k],价值为 w[i][k]。
    则状态转移方程:dp[i][j]=max⁡(dp[i−1][j],max⁡1≤k≤S(dp[i−1][j−v[i][k]]+w[i][k]))dp[i][j]=\max\left(dp[i-1][j],\quad\max_{1\leq k\leq S}(dp[i-1][j-v[i][k]]+w[i][k])\right)dp[i][j]=max(dp[i1][j],1kSmax(dp[i1][jv[i][k]]+w[i][k]))
    即,遍历该组的所有物品,找到能带来最大价值的那个物品。

  3. 初始化
    当i=0时,无论背包容量为何值,背包内没有物品,总价值为0。

DP优化

方法原理与01背包相同,不在赘述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;

int v[N][N], w[N][N];
int s[N];

int n, m;

int f[N];

int main()
{
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &s[i]);
        for(int j = 1; j <= s[i]; j ++)
            scanf("%d%d", &v[i][j], &w[i][j]);
    }


    for(int i = 1; i <= n; i ++)
    {
        for(int j = m; j >= 0; j --) 
        {
            for(int k = 1; k <= s[i]; k ++)
                if(j - v[i][k] >= 0)f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
        }
    }

    printf("%d", f[m]);

    return 0;
}

三、完全背包问题

题目描述

有N件物品和一个容量是V的背包。每种物品都有无限件可用。
第i件物品的体积是viv_ivi,价值是wiw_iwi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wivi,wivi,wi,用空格隔开,分别表示第 i件物品的体积和价值。

输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值