01背包问题学习总结(八)(5.1)

01背包问题

基本题型:背包的体积为m,有n个物品它们有不同的价值v和不同的体积w,计算背包最多能装多少价值的物品。
和贪心的区别:贪心求解背包问题用的性价比,性价比优的先装,剩下的物品可以拆分直到装满背包。动态规划01背包问题只有两种状态,1装0不装,物品不可拆分。由于只有装与不装两种状态,无需排序,但如果增加其他限制条件,也可能需要考虑排序问题。
普通方法:

memset(d,0,sizeof(d))for(i=1; i<=n; i++)
{
        for(j=0; j<=m; j++)
        {
              if(j-w[i]>=0)
                  d[i][j]=max(d[i-1][j],d[i-1][j-w[i]]+v[i]);
              else d[i][j]=d[i-1][j];
        }
}
cout<<d[n][m]<<endl;

用d[i][j]描述状态,i表示前i件物品,j表示背包的承重。因为物品只存在放(1)与不放(0)两种状态:
0时d[i][j]=d[i-1][j],因为第i个物品不放,相当于i-1的物品放到承重为j的背包里。
1时d[i][j]=d[i-1][j-w[i]]+v[i]其中j-w[i]相当于为第i个物品预留这个物品的空间。

由i和i-1想到用滚动数组减少空间复杂度,进而想尝试用一维数组减化这个问题。分析发现可以去掉i这一维,只保留后面的j变成一维数组,先上模板

memset(d,0,sizeof(d));    
for(i=1; i<=n; i++)
{
      for(j=m; j>=w[i]; j--)
      {
             d[j]=max(d[j],d[j-w[i]]+v[i]);
      }
}
cout<<d[m]<<endl;

为什么内层循环要倒过来写呢?
参考其他博主图片
在这里插入图片描述
自己做了一个简单模拟,和上面图片思路相同
在这里插入图片描述
我们发现,当内层循环倒着写的时候,我们求的d[j]还保存着前一个状态(i-1)的各个d[j]的值,相当于实现了d[i-1][j-w[i]]的功能。如果不倒着写,当在求同一个物品i的各种状态j时,由于j是从小到大循环的,所以j取的越大时必定会受到同一个i的小j的影响,举个例子:上面的动态转移方程d[j]=max(d[j],d[j-w[i]]+v[i]),当i=1时,因为for(j=w[i];j<=m;j++),w[1]=2,v[1]=6,首先更新d[2]=d[2-2]+v[1]=0+6=6,接着d[3]=d[3-2]+v[1]=0+6=6,但是到了d[4]=d[4-2]+v[1]=6+6=12,物品i的各个j受到的影响不是来自i-1个物品的,而是来自i本身,不符合01背包的逻辑,但符合完全背包的逻辑。
参考来源

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int a[1005],b[1005],d[1005];
int main()
{
    int t,i,n,v,j;
    cin>>t;
    while(t--)
    {
        cin>>n>>v;
        for(i=1; i<=n; i++)
            cin>>a[i];
        for(i=1; i<=n; i++)
            cin>>b[i];
        memset(d,0,sizeof(d));    
        for(i=1; i<=n; i++)
        {
            for(j=v; j>=b[i]; j--)
            {
                 d[j]=max(d[j],d[j-b[i]]+a[i]);
            }
        }
        cout<<d[v]<<endl;
    }
    return 0;
}

还不明白可参考这个博主的博客,总结的比较详细:
01背包问题相关优化大全(二维到一维)

需要用到排序+01背包的问题:
E
题意:有n件物品可买,你有m钱,告诉你这n件物品每件的价格pi,价值vi,以及你至少有多少钱才能买这件物品qi,求你最多能得到多少价值
为什么排序:举个例子
3 10
5 10 5
3 5 6
2 7 3
01背包问题的内循环是倒着推的,如果不排序
在这里插入图片描述
本质上相当于倒着买,先买价值为3的再买价值为6的,一共只能买9价值的物品,但我们发现如果先买价值为5的再买价值为6的也可以,一共能买价值为11的物品,显然价值更大。
•怎样排序:
为了避免这种情况,我们应该先排序,把花费大的及可能买的尽量排在后面,因为这样能保证花费小的已经购买了且花费大的也能购买,但是直接这样排是不对滴,分析一下,发现如果知道a物品(pa,qa,va)和b物品(pb,qb,vb),如果你想先买a物品再买b物品,你手里至少要有pa+qb元,如果你想先买b物品再买a物品,你手里至少要有pb+qa元,比较pa+qb与pb+qa取小值,如果pa+qb<pb+qa,即qb-pb<qa-pa,先买a,
按照以上排序,先买q-p大的更合适。

#include<iostream>
#include<algorithm>
#include<minmax.h>
using namespace std;
struct goods
{
    int p,q,v;
    bool operator<(const goods &b)const
    {
        return q-p<b.q-b.p;
    }
} a[505];
int d[5005],n,m,i,j;
int main()
{
    while(cin>>n>>m)
    {   memset(d,0,sizeof(d));
        for(i=1; i<=n; i++)
            cin>>a[i].p>>a[i].q>>a[i].v;
        sort(a+1,a+1+n);
        for(i=1; i<=n; i++)
        {   for(j=m; j>=a[i].q; j--)
                d[j]=max(d[j],d[j-a[i].p]+a[i].v);
        }
        cout<<d[m]<<endl;
    }
    return 0;
}

B
题意:食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。注意:卡上的剩余金额大于或等于5元是才可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)

因为题目中给的限制条件是卡上剩余金额大于等于5元才能购买成功,即使买后为负。为了让剩余金额最小,我们可以先买一些菜,最后剩下5元用来买最贵的菜(一定能买)。
所以先对金额排序,不考虑最贵的菜,只考虑卡上有m-5的钱,这就成了基本的01背包问题,最后再减去最贵的菜就可。

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
int v[1005],m,d[1005];
int main()
{
    int n,i,j;
    while(cin>>n&&n!=0)
    {
        for(i=1; i<=n; i++)
            cin>>v[i];
        cin>>m;
        sort(v+1,v+1+n);
        memset(d,0,sizeof(d))for(i=1; i<n; i++)
        {
            for(j=m-5; j>=v[i]; j--)//m-5保证可以购买成功
            {   d[j]=max(d[j],d[j-v[i]]+v[i])}
        }
        if(m<5)
            cout<<m<<endl;
        else
            cout<<m-d[m-5]-v[n]<<endl;
    }
    return 0;
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值