背包问题(普通、完全、多重)的思路分析和代码模板

本文介绍了如何使用动态规划解决三种类型的背包问题:普通背包、完全背包和多重背包。通过逐步优化,从暴力枚举到剪枝法,再到动态规划的一维和二维数组优化,以及二进制优化和单调队列优化,有效降低了时间复杂度,提高了算法效率。
摘要由CSDN通过智能技术生成

一、普通背包问题

问题描述:

给定 n 件物品,物品的重量和价值分别为w[i]和v[i]。现挑选物品放入背包中,假定背包能承受的最大重量为 m,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

即只需要决定放不放这个物品,不涉及放多少的问题(物品只有一个)。

直接想法:

暴力枚举(时间复杂度O(2ⁿ))

即排列组合, 枚举每一个物品是否在背包里

改进想法:

剪枝法(1.跳过超重的部分 2.相同重量且有更优解时,及时剪枝)

在将上述爆搜方法的缺点克服后, 我们已经窥得动态规划的一点端倪。或者说,动态规划本质上是一种优化的爆搜

进阶想法:

动态规划

即f数组是一个集合, 其存储的值是集合中唯一有意义的情况,时间复杂度O(nm),明显优于暴力

关于初始化,因为后面有剪枝,而且下一排需要用到它的值,这样是为了使每个f [i] [c]都能有正确的值

for(int i=0;i<n;i++)           //枚举每件物品
{
    for(int c=0;c<=m;c++)      //枚举每个重量
    {
        f[i][c]=f[i-1][c];     //初始化
        if(w[i]>c) continue;  //剪枝去超重情况
        f[i][c]=max(f[i][c],f[i-1][c-w[i]]+v[i]);  //取拿和不拿中的最大值
    }
}

一维数组优化

有的朋友就问了,这里为什么没有初始化了? 因为这是一个一维数组,每排用到的都是当前所在的值

至于内循环顺序为什么变了,因为每个物品只有一个,如果先排前面的,就有可能出现放两次的可能

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

更进一步的常数优化:

改后的内层循环只循环到最小的会改变的重量的地方,避免了部分无效循环

for(int i=1;i<=n;i++)
{
    sumw+=w[i];
    bound=max(m-sumw,w[i]);
    for(int c=m;c>=bound;c--)
    {
        f[c]=max(f[c],f[c-w[i]]+v[i]);
    }
}

二、完全背包问题

问题描述:

给定 n 件物品,物品的重量和价值分别为w[i]和v[i],每一件物品可以取无数个。现挑选物品放入背包中,假定背包能承受的最大重量为 m,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

即需要决定放不放这个物品,如果放要放多少个的问题(物品有无数个)。

直接想法:

直接在普通背包动态规划的基础上对每个物品,枚举其可能的个数(时间复杂度O(nmk))(k=m/w[i])

对于一些比较特殊的样例,可以将这个时间复杂度卡得很高

for(int i=0;i<n;i++)           //枚举每件物品
{
    for(int c=0;c<=m;c++)      //枚举每个重量
    {
        f[i][c]=f[i-1][c];     //初始化
        for(int k=0;k<=c/w[i];k++)  //枚举拿的件数
        {
            f[i][c]=max(f[i][c],f[i-1][c-k*w[i]]+k*v[i]);  //取拿不同件数中的最大值
        }
    }
}

改进想法:

二进制优化(将每次循环拿的件数从增加一件改成增加2的k次幂件直至放不下)

for(int i=1;i<=n;i++)
{
    for(int a=1;a*2<m;a*=2)
    {
        for(int c=m;c>=w[i];c--)
  		{
        	f[c]=max(f[c],f[c-a*w[i]]+a*v[i]);
    	}
    }
}

进阶想法:

一维数组优化:

其实不难发现,以下代码与普通背包一维数组优化只有内循环顺序有差别,因为这次需要包括放多个的情况

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

三、多重背包问题

问题描述:

给定 n 件物品,物品的重量和价值分别为w[i]和v[i],每一件物品对应的数量有u[i]个。现挑选物品放入背包中,假定背包能承受的最大重量为 m,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

即需要决定放不放这个物品,如果放要放多少个的问题(物品有u[i]个)。

直接想法:

直接在普通背包动态规划的基础上对每个物品, 枚举其可能的个数(时间复杂度O(nmk))

对于一些比较特殊的样例,可以将这个时间复杂度卡得很高

for(int i=0;i<n;i++)           //枚举每件物品
{
    for(int c=0;c<=m;c++)      //枚举每个重量
    {
        f[i][c]=f[i-1][c];     //初始化
        for(int k=0;k<=u[i];k++)  //枚举拿的件数
        {
            if(u[i]*w[i]>c) continue;
            f[i][c]=max(f[i][c],f[i-1][c-k*w[i]]+k*v[i]);  //取拿不同件数中的最大值
        }
    }
}

改进想法:

二进制优化(将每次循环拿的件数从增加一件改成增加2的k次幂件直至放不下或者不够,将剩余的再循环一次)

for(int i=1;i<=n;i++)
{
    for(int a=1;a*2<m&&a*2<u[i];a*=2)
    {
        for(int c=m;c>=a*w[i];c--)
  		{
        	f[c]=max(f[c],f[c-a*w[i]]+a*v[i]);
    	}
    }
    if(a*2<=m)  //如果放得下,就放剩余的
    {
        for(int c=m;c>=(m-a+1)*w[i];c--)  //(m-a+1)是剩余的数量
  		{
        	f[c]=max(f[c],f[c-(m-a+1)*w[i]]+(m-a+1)*v[i]);
    	}
    }
}

进阶想法:

单调队列优化

由于理解难度较高,以下只给出模板,具体原理可以自己去参考https://github.com/tianyicui/pack(背包九讲),原理讲的特别清晰!!!

for(int i=1;i<=n;i++)
{
    if(w[i]*a[i]>m)
    {
        for(int c=0;c<=m;c++)
        {
        if(c>=w[i])
        f[c]=max(f[c],f[c-w[i]]+v[i]);
        }
    }
    else
    {
         k=1;amount=a[i];
         while(k<amount)
         {
             for(int c=k*w[i];c>=0;c--)
             {
                 if(c>=w[i])
                 f[c]=max(f[c],f[c-w[i]]+k*v[i]);
             }
             amount-=k;
             k<<=1;
         }  
         for(int c=amount*w[i];c>=0;c--)
         {
             f[c]=max(f[c],f[c-w[i]]+amount*v[i]);
         }
    } 
}

by  SZU  jt  2022-05-19

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值