acm学习的第九周-背包问题的理解

这个周主要学习了动态规划中的背包部分
一、 01背包
N件物品放进容量为V的背包,求解价值总和最大值
模板:

f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i]);

i表示第i件物品,v表示容量,第i-1物品可以选择放或不放,放则容量减去c【i】,价值增加w【i】,不放则保留v,比较两者最大值,最后得出的f[i][v]即为最大价值
优化:转移方程只用到i-1和i两个物品,可以省略一维,用f[v]表示最大价值即可,但不能漏掉i的循环。

f[v]=max(f[v],f[v-c[i]]+w[i]);

注意01背包容量的循环是从v到c[i]为止的
例题
1.给出一些物品,每个有自己固定的价值,一个容量为m的背包,求出带出不超过背包容量最大价值的物品

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
long long int v[1005],w[1005],dp[1005];
int main()
{
   int i,n,m,j,o;
   cin>>m;
   while(m--)
   { cin>>n>>o;
  memset(dp,0,sizeof(dp));
    for(i=0;i<n;i++)
    { cin>>w[i];}
    for(i=0;i<n;i++)
    { cin>>v[i];}
    for(i=0;i<n;i++)
    { for(j=o;j>=w[i];j--)
    { dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    }
    }
    cout<<dp[o]<<endl;}
}

最基础的背包问题,直接套用模板即可
2.在饭堂买饭菜,当你的饭卡里剩下不到五元时,你就买不了任何东西,少于五元的也不可以,但是只要饭卡里超过五元,就可以买到任意价值的菜,给出几组菜的数量和菜的价钱,求怎样买能使饭卡中剩下的钱最少。

#include <iostream>
#include <algorithm>
#include <string.h>
#include <cmath>
using namespace std;
int c[10001],w[10001],dp[10001];
int main()
{
    int i,j,v,n,maxx=0;

    while(cin>>n)
    {  memset(dp,0,sizeof(dp));
      memset(c,0,sizeof(c));
        if(n==0) break;
        maxx=0;
        for(i=0;i<n;i++)
    { cin>>c[i];}
       cin>>v;
    sort(c,c+n);
   if(v>=5) {
    for(i=0;i<n-1;i++)
    { for(j=v-5;j>=c[i];j--)
    {
      dp[j]=max(dp[j],dp[j-c[i]]+c[i]);
      }
    }
    cout<<v-dp[v-5]-c[n-1]<<endl;}
    else cout<<v<<endl;}
}

既然饭卡必须要超过五元,那么就使饭卡的“容量”减去五元,求出在容量减少5时能购买到的最贵的菜的总价值,此时只能在买一个菜,再通过排序找出所给菜中最贵的,那么饭卡花的钱就是最多的,饭卡剩下的钱也就自然是最少的了,如果一开始饭卡的钱就不超过五元,就直接输出。
3.方案数问题
这道题可以用dp[i][j][k]来表示前i个物品选j个总价钱为k的方案数,但是我在看题解的时候发现另一种方法,虽然花了点时间理解他们的意思(某些博主给的思路是真简洁。。。)
题目是给出t组数据,有n个物品,m的钱,求出用这些钱买到最多件物品的方案数

#include <iostream>
#include <algorithm>
#include <string.h>
#include <cmath>
using namespace std;
int dp[501][2],v[501];
int main()
{
   int i,j,m,t,n;
   cin>>t;
   while(t--)
   { memset(dp,0,sizeof(dp));
    cin>>n>>m;
    for(i=0;i<n;i++)
    { cin>>v[i];}
    for(i=0;i<m;i++)
    { dp[i][1]=1;}
    for(i=0;i<n;i++)
    { for(j=m;j>=v[i];j--)
    {  if(dp[j][0]==dp[j-v[i]][0]+1)
      dp[j][1]=dp[j][1]+dp[j-v[i]][1];
      if(dp[j][0]<dp[j-v[i]][0]+1)
      { dp[j][0]=dp[j-v[i]][0]+1;
       dp[j][1]=dp[j-v[i]][1];}}}
       if(dp[m][0]==0) cout<<"Sorry, you can't buy anything."<<endl;
       else cout<<"You have"<<" "<<dp[m][1]<<" "<<"selection(s) to buy with"<<" "<<dp[m][0]<<" "<<"kind(s) of souvenirs."<<endl;}

return 0;

}

首先要给方程设定边界值,除了什么都买不到的情况外,方案数的初始值都是1,用dp[j][0]表示买到物品数量,用dp[j][1]表示方案数,将所有物品的价值都设定为1,力求买到最多的物品,然后分以下几种情况,当出现dp[j][0]==dp[j-v[i]][0]+1时表示买到同种数量的物品出现了不同的买法,此时的方案数就是二者方案数相加的和,第二种情况就是当出现购买物品数量比之前多的时候,背包和方案数就都要替换成新的情况,因为题目说的是购买最多的物品,第三种情况购买物品数量比其他情况少,就不用管了,最后判断dp[m][0]的值(如果值是0就说明什么也没买到),最后按格式输出 dp[m][0](最多物品数) dp[m][1](方案数) 即可
二、完全背包
同样是N件物品容量为v的背包,但是每件物品都有无限件可用,那么对于背包就可能装很多件相同的物品
完全背包也可以直接套用01的模板,也可以用一维数组存储,只是容量的循环变为了从c【i】——v
背包的问题都有很多相似之处,但是这道题有需要注意的点:
一个储钱罐,能装硬币的质量m可以计算出来,有n种硬币及其重量,还有其价值,问最少用多少钱可以装满罐子,如果不能装满输出This is impossible
这类问题是存在无法凑整的情况的,我们先用f[i]表示所需钱数,给其赋值INF(很大的一个数)

for(i=0;i<n;i++)
{ for(j=w;j<=m;j++)
{  f[j]=min(f[j],f[j-w]+p);}
}   

用这个循环可以求出所需最小钱数f[m],自然会替代INF的值,但如果它仍等于INF,代表无法用硬币装满这个罐子。
三、多重背包
完全背包的升级版,它会给出每件物品具体的数量,它与完全背包最大的不同就是它可能会出现装完多个某种物品后背包还没满而需要装其他物品的情况。
可以用二进制进行优化,也可以通过判断看其是否具有转化为完全背包的条件,复杂的背包问题其实也可以转化为01背包。
感想:
随着动态规划学习的深入,我了解了很多知识,也记下了一些做题方法,但是也存在着很多不懂的地方,动态规划的题种类很多,有些也很复杂,新的样式层出不穷,在多刷题学习做题方法的基础上,最重要的还是真正的理解动态规划的知识,这样才能更好更准确的修改代码来应对多变的情况,但我明显还没有做好这一点。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值