学习动态规划DP(二)——背包问题

前言:
学习动态规划,必学背包问题。背包问题可以说是动态规划里的经典、基础问题了。第一次接触时真的懵了很久,现在重新来看明朗了很多,所以想趁这时候写下自己的想法和理解。

感觉动态规划可以拆成:动、态、规划。
态:我们所要解决的问题在当前状态下会有不同的发展方向,或者说下一个状态有多种选择;
动:前一个状态可能会对后面的状态造成一定的影响,状态之间是有联系的,而不是各顾各的,“静态”的。
规划:我们需要规划好在往下一个状态发展时,可以有几种选择(确保考虑了所有可能的情形),并确定好每一种选择对应的不同状态。(我们要做的,就是根据题目的要求,在上述的状态中选取我们要找的状态。)

总而言之,动态规划是一种大的思想,没有什么函数模板可以套用。我们需要学习并且理解这一思想,避免“每次碰到新题自己都想不出来,但一看题解就懂”的情况(紫书上的话哈哈哈)。其中最核心的,也就是状态的选择的转换了,还是得多做题目练习,久了自然会参悟。(大佬忽略我这句话)

进入正题:经典背包问题
背包问题,其实就是物品是否放进背包的问题,对于每个物品,就两种情况:放 和 不放

下面加粗的字是为了区别三种问题的不同。
一、01背包
有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。每种物品数量都为1!

思路:一个物品有两个参数,我们可以根据物品的参数去定义当前的状态,对状态的不同定义会导致出现不同的状态转换方程,难度有异,所以我们要慎选。
对于这类问题,我们可以定义一个二维数组(对应物品的两个参数) d[i][j],表示把前 i个物品装到容量为 j的背包中可以获得的最大总价值。选择好状态的表示方式后,我们开始考虑从当前状态进入到下一个状态时的不同选择。对于背包问题,只有选与不选两种情况,所以有两种不同选择。
①不选:则显然,当前的背包容量和总价值没有变化,但我们物品的选择范围增加了一,所以 d[i][j] = d[i-1][j];
②选:为了达到总价值最大的目的,我们要把选后的总价值跟原来的总价值比较,取最大的。可能有人会说,再选一件物品加入,那总价值肯定大了啊怎么还要比较。不要忘了此时当前背包容量也变化了。我们要明确我们定义的状态是什么,是把前 i个物品装到容量为 j的背包中可以获得的最大总价值,我们对这前 i 件物品的选取方式有多种(组合),我们要在当前容量一定的情况下选择最佳的那种,这样就不是单纯的再选取一个加进去了。回归正题,此时 d[i][j] = max(d[i][j],d[i-1][j-v[i]]+w[i])。
我们来好好剖析一下这个方程中的 d[i-1][j-v[i]]+w[i]。d[i-1][j-v[i]]指把前i-1件物品装到容量为j-v[i]的背包中可以获得的最大总价值,这时候我们再把第i 件物品加入,就变成了把前i 件物品装到容量为 j-v[i]+v[i]==j的背包中可以获得的最大总价值,即d[i][j]。所以d[i][j] = 原来没加入i 时d[i-1][j-v[i]]的总价值,再加第i 件物品的价值w[i]。

for(int i=1; i<=number_of_object; ++i)
    {
        for(int j=0; j<=capacity_of_bag; ++j)
        {
            //如果容量不够,那肯定放不下
            if(j<v[i])
                d[i][j]=d[i-1][j];
            //容量够了,选择放或者不放后总价值最大的一个
            else
                d[i][j] = max(d[i-1][j],d[i-1][j-v[i]]+w[i]);
        }
    }
//最终答案就是d[number_of_object][capacity_of_bag]了。

我们还可以这样定义状态:d[i]表示背包容量还剩下i 时可以装的最大总价值。变成了一维数组,也占用了更多的空间。

cin>>n>>m;//n件物品,背包容量m
    for(int i=1;i<=n;++i)
    {
        cin>>w>>v;//第i 件物品的价值和体积
        //从最大容量遍历,是为了使得同一件物品不重复放进背包
        //因为我们每一步都是给f[j]赋值,且是根据我们还未遍历到的j来赋值
        //所以f[m-v-1]并没有在f[m]的基础上再加上i 的价值
        //若m>2*v,在j=m时我们把可以 i 放进背包,因为此时j<m还未开始遍历
        //在j=m-v-1时我们把可以 i放进背包,因为此时j<m-v-1还未遍历
        //相当于在原来没有i 的情况下加入了i .
        //这时候背包里并没有两个i ,因为i 存在于f[m-v-1]中,所以也必然存在于f[m]中。
        //就像是把i 存在的范围缩小了一样。再强调f[m-v-1]并没有在f[m]的基础上再加上i 的价值。

        for(int j=m;j>=v;--j)
        {
            //如果把i 添加后总价值变大,就更新f[j]
            //原理同上述的二维数组解法
            if(f[j]<f[j-v]+w)
                f[j]=f[j-v]+w;
        }
    }
    cout<<f[m]<<endl;

01背包基础习题

二、完全背包
有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。每种物品都有无限多个

01背包的进阶,有无限多个物品,所以可以重复选择。其实思路跟01背包一样,直接用代码解释。我学习的时候感觉一维数组好理解,所以直接用一维数组。

cin>>n>>m;//n件物品,背包容量m

    for(int i=1;i<=n;++i)
    {
        cin>>v>>w;//第i 件物品的体积和价值
        //从 j = v开始循环(从容量小到容量大),可以使得每一个物品被重复选中
        //j=v时我们将i 放入,j=2*v时我们也将i 放入,
        //此时的d[2*v]是在d[v]的基础上再加上i 的价值
        //也就选中了两个i 放入背包,因此实现了重复选中物品
        for(int j=v;j<=m;++j)
            if(d[j]<d[j-v]+w)
                d[j]=d[j-v]+w;
    }
cout<<d[m]<<endl;

完全背包基础习题

三、多重背包
有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。第i 种物品有c[i]件。

不同种类物品的个数可以不同。跟完全背包一样,也是建立在01背包的基础上的。如果一种物品有k 件,我们可以看成有k 种参数相同的物品,每种只有一件,这样就完全转为01背包问题去求解了!

cin>>n>>m;//n件物品,背包容量m
for(int i=1;i<=n;++i)
    cin>>v[i]>>w[i]>>c[i];//分别表示第I种物品的体积、价值、数量
        //前两个for表示了:如果一种物品有k 件,
        //我们可以看成有k 种参数相同的物品,每种只有一件
        for(int i=1;i<=n;++i)
            for(int k=1;k<=c[i];++k)
                //从最大容量开始遍历,就是因为思路转换后变成解决01背包问题
                for(int j=m;j>=v[i];--j)
                    if(d[j]<d[j-v[i]]+w[i])
                        d[j]=d[j-v[i]]+w[i];
cout<<d[m]<<endl;

多重背包基础习题

总结完成!对DP的学习暂时放下了,期末复习要紧。
用一维数组虽然简便但占空间多且不利用输出具体的选择方案。
如何输出具体的选择方案,以后再学。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值