5. 多重背包问题 II(acwing)

5. 多重背包问题 II

题目描述

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

第 i种物品最多有 si 件,每件体积是 vi,价值是 wi

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

输出最大价值。

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

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N≤1000

0<V≤2000

0<vi,wi,si≤2000

提示:
本题考查多重背包的二进制优化方法。

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

动态规划

一维数组

三重循环(超时)

这段代码是用于解决多重背包问题的,它使用了动态规划(DP)的方法,并且通过二进制的思想对多重背包问题进行了优化。不过,请注意,这个代码并没有实际使用二进制优化,而是使用了一个普通的三重循环方法。下面我会注释这段代码,但也会提到二进制优化原理。

#include<bits/stdc++.h>  // 包含了几乎所有常用的库
using namespace std;

// 定义三个数组,分别存储物品的体积、价值和数量
int v[1010], w[1010], s[1010];
// 定义一个一维数组dp,用于存储背包的容量与可达到的最大价值
int dp[2020];

int main()
{
    int n, m;  // n为物品种类,m为背包容量
    cin >> n >> m;  // 输入物品数量和背包容量
    for(int i=1; i<=n; i++)
        cin >> w[i] >> v[i] >> s[i];  // 输入每个物品的体积、价值和数量
    // 遍历每种物品
    for(int i=1; i<=n; i++)
        // 从背包容量开始向下遍历,这样可以确保每个物品只选择一次
        for(int j=m; j>=w[i]; j--)
            // 遍历可以选择的物品数量,注意不超过背包容量和物品数量限制
            for(int k=0; k<=s[i] && k*w[i]<=j; k++)
                // 更新dp[j]为不选择或选择k个第i种物品的最大值
                // dp[j-k*w[i]]+k*v[i]表示选择k个第i种物品的价值
                dp[j] = max(dp[j], dp[j-k*w[i]]+k*v[i]);
    // 输出背包容量为m时的最大价值
    cout << dp[m];
    return 0;
}

上面的代码是对多重背包问题的直观解决方法,在这种方法中,我们对于每种物品,都尝试从0件到s[i]件之间的所有可能性,并更新状态dp[j]。这是一种朴素的方法,动态规划状态转移方程如下:

dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]),其中k是选择的第i种物品的数量,这个方程表示背包容量为j时最大价值可以是不选当前物品的价值dp[j],或者选了k个当前物品的价值dp[j - k * w[i]] + k * v[i]

尽管这个代码段没有使用二进制优化,但是二进制优化的思想是:将每种类型的物品数量按照二进制拆分,即将每种物品分解为1件、2件、4件…,2k件(其中2k不超过s[i]),通过这种方式可以将多重背包问题转换为0/1背包问题来解决,从而优化时间复杂度。

这段代码是完整的,并且可以正确地解决多重背包问题,但在面对大数据集时可能不够高效,那时候二进制优化就显得非常必要了。在实际应用中,你可以根据数据的大小和时间复杂度要求,来决定是否使用二进制优化。

二进制优化(正确代码)

这段代码是多重背包问题的解决方案,使用了二进制优化方法。详细注释如下:

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

// 设置最大数组范围
const int z=25000;
// v数组保存每个拆分后的物品的价值,w数组保存每个拆分后的物品的体积
int v[z],w[z]; 
// dp数组用于动态规划,记录背包容量为j时可达到的最大价值
int dp[z]; 

int main()
{
    int n,m; // n为物品种数,m为背包容积
    cin>>n>>m; // 读入物品种数和背包容积
    int pos=0; // pos用于记录拆分物品的个数
    // 循环处理每种物品
    for(int i=1;i<=n;i++)
    {
        int a,b,s; // a为体积,b为价值,s为数量
        cin>>a>>b>>s; // 读入体积、价值、数量
        int k=1; // 用于二进制拆分,初始为2的0次方,即1
        // 当物品数量大于等于k时进行拆分
        while(k<=s)
        {
            // 按照二进制拆分原则,存储拆分后物品的体积和价值
            w[++pos]=a*k; // 拆分的体积是原体积的k倍
            v[pos]=b*k; // 拆分的价值是原价值的k倍
            // 减去已经拆分的数量
            s-=k;
            // k翻倍进行下一次二进制拆分
            k*=2;
        }
        // 如果还有剩余物品,则继续存储剩余的体积和价值
        if(s>0)
        {
            w[++pos]=a*s; // 剩余的体积
            v[pos]=b*s; // 剩余的价值
        }
    }
    // 使用一维数组的动态规划求解,类似于01背包问题
    for(int i=1;i<=pos;i++) // 遍历所有拆分后的物品
    {
        // 从大到小遍历背包容量,确保每个物品只被计算一次
        for(int j=m;j>=w[i];j--)
        {
            // dp[j]是不放这件物品,dp[j-w[i]] + v[i]是放这件物品
            // 更新dp[j]为这两者的较大值
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    // 输出背包容量为m时,能装入物品的最大价值
    cout<<dp[m];
    return 0;
}

在这个多重背包问题中,每种物品可以有多件,但是数量有限。二进制优化的思路是将每种物品拆分成若干个组合,使得每个组合内物品的数量是2的幂次,这样可以通过少量的组合来组成任意数量的物品,减少了状态的数量。优化后使用动态规划求解时,就如同解决01背包问题一样,每个物品只会被选取一次。最后输出的 dp[m] 就是所求的最大价值。

二维数组

三重循环(超时)

这段代码是一个解决多重背包问题的例子,它使用了动态规划的方法。具体来说,它利用二维动态规划数组dp[i][j]来存储考虑前i种物品,当背包容量为j时所能达到的最大价值。下面是对这段代码的详细注释:

#include<bits/stdc++.h> // 包括STL库
using namespace std;

// 定义数组,v[i]、w[i]和s[i]分别存储第i种物品的体积、价值和数量。
int v[1010], w[1010], s[1010];
// dp[i][j]表示考虑前i种物品,当背包容量为j时的最大价值。
int dp[2020][2020];

int main()
{
    int n, m; // n是物品种数,m是背包容量
    cin >> n >> m; // 输入物品种数和背包容量
    // 读入每种物品的体积v[i]、价值w[i]和数量s[i]
    for(int i = 1; i <= n; i++)
        cin >> w[i] >> v[i] >> s[i];

    // 动态规划求解
    for(int i = 1; i <= n; i++) // 遍历所有物品
    {
        for(int j = 0; j <= m; j++) // 遍历所有可能的背包容量
        {
            for(int k = 0; k <= s[i] && k * w[i] <= j; k++) // 遍历当前物品可以选择的数量,确保不超过该物品的数量限制且背包容量不会超标
            {
                // dp[i][j]更新为考虑当前i种物品、容量为j时的最大价值
                // max函数比较不取当前物品的情况和取k个当前物品情况下的价值,选择较大者
                dp[i][j] = max(dp[i][j], dp[i-1][j-k*w[i]] + k*v[i]);
            }
        }
    }
    // 输出考虑n种物品,背包容量为m时的最大价值
    cout << dp[n][m];
    return 0;
}

动态规划原理解释

这个动态规划的过程是按照物品的种类进行的,对于每一种物品i,它都会尝试从0件到s[i]件进行选择,并更新状态dp[i][j]。这里的dp[i][j]代表考虑到第i种物品时,背包容量为j能够达到的最大价值。

  • dp[i-1][j-k*w[i]] + k*v[i]的意思是,如果当前考虑选择k个第i种物品,那么背包里剩余的容量为j-k*w[i],对应的最大价值是dp[i-1][j-k*w[i]](即在没有考虑第i种物品时的最大价值),再加上k个第i种物品的价值k*v[i]

这种方法遍历了物品的每种可能性,确保找到在不超过背包容量的情况下,物品的最大总价值。通过更新dp[i][j],最终dp[n][m]存储的就是在考虑所有n种物品,且背包容量为m时的最大价值。

二进制优化(超出内存限制)

下面是代码的详细注释,解释了每一部分的作用和实现的功能:

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

// 定义最大的数组范围
const int z=8000;
// 定义价值和重量数组
int v[z],w[z];
// 定义动态规划的数组,dp[i][j]表示前i件物品在不超过j体积时的最大价值
int dp[z][z];

int main()
{
    // n 表示物品种类数,m 表示背包的容量
    int n,m;
    cin>>n>>m;
    // pos 用于记录当前拆分后的物品的个数
    int pos=0;
    
    // 遍历每种物品
    for(int i=1;i<=n;i++)
    {
        // a 表示每件物品的体积,b 表示每件物品的价值,s 表示物品的数量
        int a,b,s;
        cin>>a>>b>>s;
        int k=1; // k 用于表示当前拆分的物品件数,初始为1,即2^0

        // 二进制拆分物品
        while(k<=s)
        {
            // 拆分物品加入数组,体积和价值都按照拆分的件数k倍增
            w[++pos]=a*k;
            v[pos]=b*k;
            // 减去已经拆分的物品数量
            s-=k;
            // 更新 k 为下一次拆分的件数,即翻倍
            k*=2;
        }
        // 如果还有剩余未拆尽的物品,则继续拆分剩下的
        if(s>0)
        {
            w[++pos]=a*s;
            v[pos]=b*s;
        }
    }

    // 动态规划处理每个物品
    for(int i=1;i<=pos;i++)
    {
        // 遍历背包的容量
        for(int j=0;j<=m;j++)
        {
            // 不选第i件物品的情况,价值等同于没有第i件物品时的价值
            dp[i][j]=dp[i-1][j];
            // 选第i件物品的情况,只有当背包容量足够时考虑
            if(j>=w[i])
                // 选和不选的最大值
                dp[i][j]=max(dp[i][j], dp[i-1][j-w[i]] + v[i]);
        }
    }

    // 输出所有物品处理完后,背包容量为m时的最大价值
    cout<<dp[pos][m];
    
    return 0;
}

这段代码的主要目的是通过二进制拆分将多重背包问题转化为01背包问题,并利用动态规划求解。二进制拆分是一种优化手段,它通过拆分物品数量来减少状态转移的次数,这在处理多重背包问题时非常有效。在动态规划阶段,由于物品已经被拆分,代码实际上是在按照01背包问题的方式处理每件物品。最后通过动态规划数组 dp 来得出最大价值。

  • 33
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

命运从未公平

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值