zoukankan      html  css  js  c++  java
  • 01背包详讲

     

    一.动态规划与递推解决01背包

    初步分析:

    0. 浅谈问题的分解

    在处理到第i个物品时,可以假设一共只有i个物品,如果前面i-1个物品的总的最大价值已经定下来了,那么第i个物品选不选将决定这1~i个物品能带来的总的最大价值

    刚刚是自顶向下,接下来反过来自底向上,第1个物品选不选可以轻松地用初始化解决,接下来处理第i个物品时,假设只有2个物品就好,那他处理完后前2个物品能带来的最大总价值就确定了,这样一直推下去,就可以推出前n个物品处理完后能带来的最大总价值

    1.分层考虑解决"每个物品最多只能装一次"

    每个物品只能装一次,那么就应该想到常用的一种方法,就是用数组的纵轴来解决,对于n个物品,为它赋予i=1~n的编号,那么数组的纵轴就有n层,每层只考虑装不装这个物品,那么分层考虑就可以解决最多装一个的问题了

    2.对0,1的理解

    对于每个背包,都只有0和1的情况,也就是拿或者不拿两种情况

    如果拿:那么空间就会减一点,比如说现在在考虑第i个物品拿不拿,如果说当前剩余空间为j,那么拿了之后空间就变为j-c[i],但是总价值却会增加一点,也就是增加w[i]

    如果不拿:那么空间不会变,还是j,但是总价值也不会变化

    3.限制条件

    所以对于这题来说有一个限制条件,就是空间不超出,然后目标就是在空间不超出的情况塞入物品使总价值最大,在前面,我们已经讲了数组的纵轴用来表示当前处理到第几个物品,那么只靠这个是不够的,而且这个数组的意义还没有讲

    这题就是限制条件(空间)与价值的平衡,你往背包中塞东西,价值多了,可是空间少了,这空间本来可能遇到性价比更高的物品但也可能没遇到

    4.具体的建立数组解决问题

    有了前面的限制情况和0,1的分析就可以建立数组了

    对于这个数组,结合题目要求来说,数组的意义肯定是当前的总价值,也就是第i个物品的总价值,那么题目还有一个限制条件,只靠一个n层的一维数组是不够的,还需要二维数组的横轴来分析当前的剩余容量

    所以我们有了一个数组可以来解决问题了,这个数组就叫f好了,然后它是一个二维数组,它的纵轴有i层,我希望它从i=1~n,不想从下标0开始是为了美观,然后这个二维数组的横轴代表着当前剩余的空间,就用j来表示,j=0~V,0就是没有空间的意思,V前面说了,是这个背包的总容量

    为什么要遍历v~0?

    其实这个就是为了遍历放这个 i 物品的所以空间情况

    我们把这个二维数组建立在int main()的上面,所以它一开始全部都是0,省去了接下来赋初值为0的功夫

    有了数组f[i][j],然后对于每个f[i][j],它表示的是已经处理到第i个物品了,当剩余空间还有j时,能带有的最大价值,也就是说f[i][j]存储的是总价值

    说是总价值,可是涉及到放物品还是不放物品的问题,所以再细致点就是:当前剩余空间为j,用这j空间取分析第i个物品装不装如,处理执行完行为后,f[i][j]就表示了当前能装入的最大价值

     

    5.推导递推方程

    一个物品,有放与不放两种选择,我们的最终结果就是综合所有物品放与不放的选择来确定。
    有这样一个状态方程:
    F[ i ][ j ]=max( F[ i-1 ][ j ] ,F[ i-1 ][ j - wi[i] ] + vi[ i ] );
     
    其实不难理解,F[ i ][ j ]  代表 当放到第 i 个物品时,此时容量还剩 j ,我们可以选择放或者不放;
    不放的话 F[ i ][ j ]=F[ i-1 ][ j ];就是相当于放了前 i-1 个物品中的物品;注意,不一定是前 i-1 个
    物品全都放进了背包。第 i 个物品放进去的话 F[ i ][ j ]=F[ i-1 ][ j - wi[i] ] + vi[ i ]; 放进去了所以
    在前面放了 i-1 个物品中的物品的基础上,容量减去 wi[i], 价值加上vi[ i ](但得在wi[i] <= j 条件下)

    为什么要用max函数? 

    有一些偏极端情况,放入这个物品,也许它价值w[i]很高,但是它占用空间c[i]也大,它的性价比可能很低,所以这时候就需要max函数了

    当还有空间时:F[i,j] = max[F[i−1,j],F[i−1,j−C[i]] + W[i]

    当空间不够时:F[i,j] = F[i−1,j]

    下面这个就是01背包的普通代码:

     1 #include<bits/stdc++.h>//万能头文件
     2 #define ll long long
     3 using namespace std;
     4 const ll maxn=100;
     5 ll n,v,f[maxn][maxn];
     6 ll c[maxn];//每个物品占用空间
     7 ll w[maxn];//每个物品的价值
     8 int main()
     9 {
    10     cin>>n>>v;
    11     for(ll i=1;i<=n;i++)
    12         scanf("%lld",&c[i]);
    13     for(ll i=1;i<=n;i++)
    14         scanf("%lld",&w[i]);
    15     for(ll i=1;i<=n;i++)//第i个物品
    16         for(ll j=v;j>=0;j--)//剩余空间j
    17         {
    18             if(j >= c[i])//如果装得下
    19                     f[i][j]=max( f[i-1][j-c[i]]+w[i],f[i-1][j]);
    20             else//如果装不下
    21                 f[i][j]=f[i-1][j];
    22         }
    23     cout<<f[n][v]<<endl;//输出答案
    24 
    25 }

    二.01背包的空间优化

    有了前面基础版01背包的学习,现在学习这个就容易多了

    1.何为空间优化,为什么要空间优化

    在01背包中通过对数组的优化(用了滚动数组的方法),可以使本来N*V的空间复杂度降低到V,也就是把关于第几个物品的N去掉了(下面会解释为什么可以这么做)

    至于为什么要空间优化,首先是因为递推本来就是用空间换时间,消耗的空间比较大,然后关于算法的竞赛一般都会有空间的限制要求,最后,在找工作面试时,面试官肯定会问一些优化的问题,平时养成优化的习惯面试时也有好处

    2.为什么这题可以降维

    通过观察可以发现对于普通版的01背包递推式,f[i][...]只和f[i-1][...]有关,那么我们可以用一种占用,一种滚动的方法来循环使用数组的空间,所以这个方法叫滚动数组,对于将来肯定用不到的数据,直接滚动覆盖即可,具体的如何滚动会放下面讲

    还有就是滚动数组的缺点是牺牲了抹除了大量数据,不是每道题都可以用,但是在这,答案刚好是递推的最后一步,所以直接输出即可,递推完后不需要调用那些已经没了的数据,所以这题可以

    下面先画个图理解一下滚动的大致概念

    反正就是不断覆盖的过程

    3.这题如何具体优化

    下面开始具体化的分析

    对于第i层,它只和第i-1层有关,但是对于剩余空间j无法优化,所以现在拿i开刀,把他砍掉,用一个长度为V(总空间)的数组来表示,然后每次相邻的两个i和i-1在上面一直滚动

    所以现在建立一个数组f[V],一维数组大小为V

    首先建立两个复合for循环

    for(i=1~n)

      for(j=v~0)

    记住这里第二层循环必须是v~0而不是0~v,先记着,后面会解释,

    接下来的分析建议配合下面图片学习

    然后在循环的过程中,还是老样子,假设我们已经循环到i=2这层了(也就是说i=1已经循环完了),然后对于i=2这一层,我们对j循环,j从v到0

    假如现在j=v,我们让f[j]=max(f[j],f[j-c[i]]+w[i])

    在没有覆盖之前,所有的f数据都是属于上一层也就是第一层的,我们就当作i-1层数据已经准备好了,然后把max内的拆成两半分析,对于f[j]=f[j]就是不放的情况,那么总价值没有改变,所以对于f[j]=f[j]就是形式上的更新数据,把i-1层的f[j],给了i-1层的f[j]...对于f[j]=f[j-c[i]]+w[i],那个w[i]是肯定要加的不用讨论,然后我们观察一下,对于下标j-c[i]是不是肯定会小于j,那么如果说j从V~0也就是从最大到最小,每次赋值处理都是从前面的格子看看数据参考,并没有修改

    再详细点说的话就是对于f[j]=f[j-c[i]]+w[i],f[j-c[i]]是第i层的东西,让j=v~0是为了让f数组每次滚动覆盖时都是覆盖接下来不需要用的位置,比如说处理到第f[8]位时,假如接下来的max判定后面那种方法总价值大,然后假设c[i]=3,这时后就相当与f[8]=f[8-c[i]=5]+w[i],我们这里只是参考了f[5]的数据,并没有改变它,因为说不定计算新一轮f[6]时又要用到旧的f[5]呢,可是我们刷新了f[8]的数字后,再j--,计算f[7],再j--,计算f[6],都不会再用到f[8]这个数据,这是由于f[j-c[i]] 中的减c[i]导致的,反之,假若我们让j=0~v,就可能出现新数据被新数据覆盖的结果,我们是有"底线"的,只允许新数据覆盖旧数据

    对于j,如果要处理f[j]=max(f[j],f[j-c[i]]+w[i]),就得当j>=c[i]时处理,因为如果j<c[i],那么j-c[i]为负,下标负的情况没必要考虑,如果考虑了还可能会溢出

     其实对于max,还用另一个小东西代替,有没有发现,如果f[j-c[i]]+w[i]>f[j],就选f[j-c[i]]+w[i],如果f[j-c[i]]+w[i]<f[j],那选f[j]和没选一样,所以待会的空间优化版省掉了max函数,少用一种函数

    #include<bits/stdc++.h>//万能头文件
    #define ll long long
    using namespace std;
    const ll maxn=100;
    ll n,v,f[maxn];
    ll c[maxn];//每个物品占用空间
    ll w[maxn];//每个物品的价值
    
    int main()
    {
        cin>>n>>v;
        for(ll i=1;i<=n;i++)
            scanf("%lld",&c[i]);
        for(ll i=1;i<=n;i++)
            scanf("%lld",&w[i]);
        for(ll i=1;i<=n;i++)//第i个物品
            for(ll j=v;j>=0;j--)//剩余空间j
            {
                if(f[j]<=f[j-c[i]]+w[i] && j-c[i]>=0 )//二维数组变一维数组
                     f[j]=f[j-c[i]]+w[i];//如果值得改变并且j的空间还装得下就赋新值
            }
        cout<<f[v]<<endl;//输出答案
    
    }

    三.初始化的细节

    原博客:https://www.cnblogs.com/zyacmer/p/9961710.html 

  • 相关阅读:
    Custom Roles Based Access Control (RBAC) in ASP.NET MVC Applications
    How To Display Variable Value In View?
    How do negative margins in CSS work and why is (margin-top:-5 != margin-bottom:5)?
    Async/Await FAQ (Stephen Toub)
    Async and Await (Stephen Cleary)
    Change Assembly Version in a compiled .NET assembly
    C# Under the Hood: async/await (Marko Papic)
    Bootstrap form-group and form-control
    Infralution.Localization.Wpf
    cocos2d-x 图形绘制
  • 原文地址:https://www.cnblogs.com/-Ackerman/p/11228249.html
Copyright © 2011-2022 走看看