• 无可救药的背尼玛和包尼玛


    ACM刷了一年多,背包学了无数次,学一次忘一次我也是醉了。

    特此来纪念一下傻逼的我学习背包的结果。。。

    顺便教教可爱又迷人的女朋友。

    ===========================尼玛,背了个包====================================

    【包】:

        首先我们要来说一下这个包,背的这个包不是普通的包,因为普通的包不会这么恶心的(生无可恋)。。。

        这个包听说是个双肩包,不过我喜欢单肩包,这可能就是我记不住这个包的原因吧。。

        包有两个参数,一个是可装的总体积 V;还有一个就是可以获得的总重量 W(一般我们也把这个重量表示为价值);

        同时呢,我们往里塞的东西也是有相应的两个参数 v[ i ] , w[ i ] ;

        两个参数对应的就是它占用的体积 和 他带来的价值;

        好了,背景介绍完毕!

    <  0 - 1 背了个包  >************************

       0-1 背包就是背包的精神内涵了(可怕.jpg)。

       【基本描述】: 给n个物品,每个物品有v[ i ],w[ i ];每个物品只有1个,问!这个包最大能装多少的价值;

       【掰扯掰扯】:傻逼一点的人肯定会想找性价比高的放,当然是不行的喽。反例自举。

              然后就诞生了0-1来背这个包;

              我们设 Dp[ i ] 表示:当背包的容量为 i 的时候,可以装入的最大的价值,(Dp[ i ]表示获得的最大价值);

              然后对于每一件物品(假设当前考虑的物品是第 j 件),我们枚举一下这件物品 放 还是 不放 到这个包里;

              ①:放

                因为我当前最大的容量是 i ,所以我放这个物品进去就要给它空出来它这么大的容量v[ j ],

                而且,空出来的这段容量所带来的价值是确定的,

                就是w[ j ],所以此时,我背包的总的价值就是 Dp[ i ] = Dp[ i - v[ j ] ] + w[ j ];

                (假设Dp[ i - v[ i ] ]是已知的);

              ②:不放

                不放就是不放,就没有什么好说的了,此时的最大的价值就是 Dp[ i ] 它原来的值了;

              ③:你到底放还是不放啊!

                当然是找两种方式中较大的一种喽。。

                原因很简单嘛,加入此时 i = V, 那么此时就是最后的结果了,我当然是要大的那个喽。

                其实更理论一点的说法是,动态规划 思维就是:通过子问题最优解得到全局的最优解,

                所以每一步我们都要得到最优解。

                然后我们的算法过程就很简单了:

                    for(int j = 0; j < n; j++)
                    {
                        for(int i = v; i >= v[j]; i--)
                        {
                            Dp[i] = max( Dp[i] , Dp[i-v[j]] + w[j] );
                        }
                    }

              代码中有一些需要解释的是,第一层枚举我当前的物品 j ,第二层是从最大的容量开始考虑的,

              为什么呢,其实很简单,因为每个物品只能用一次,

               i - v[ i ] 是比 i 容量小的状态,如果我们从小往大的枚举的话,那么在 i - v[ j ] 的状态的时候,

              如果我们选择了放 j 进去,就意味着j 被用掉了,

              并且改变了Dp[ i - v[ j ] ] 的值,我们后来的枚举就不能保证只使用一次 j 了。

              倒着枚举可以避免这种状况的出现;

              [ 注 ]: 

                使用前要记得把Dp数组清空为 0 ;

              [ 问题变化 ]:

                ①:问你恰好装满的最大价值。

                  解决这种问题的时候只要把Dp数组初始化改变一下,DP[ 0 ] = 0; 其余的Dp全部赋为 [ 负无穷 ]。

                  [ 负无穷 ] 表示该种容量的时候不合法,这样在更新的时候,不合法的背包始终都会是负数,

                  而合法的背包容量就是正数。

                  最后判断一下Dp[ V ] 是否是负数就知道是不是合法的结果了。

     <  完全 背了个包  >************************

         完全背包就是0-1背包的简化版了。

        【基本描述】:给你n个物品,每个物品还是那两个参数,但是每一个都可以使用无限多次;

        【随便扯扯】:唯一的不同就是枚举的顺序,这次是从前往后的了。至于为什么不懂自己想。

                  for(int j = 0; j < n; j++)
                  {
                      for(int i = v[j]; i <= V; i++)
                      {
                          Dp[i] = max( Dp[i] , Dp[i-v[j]] + w[j] );
                      }
                  }

               其它的东西都是一样的了,包括初始化和问题变种;

     <  多重 背了个包  >************************

         多重背包就是完全背包和0-1背包的合体版了(就是这么可怕)。

        【基本描述】:给你n个物品,每个物品还是那两个参数,但是每一个都可以使用的次数是给定的;

        【随便扯扯】:唯一的不同就是枚举的顺序,这次不是从前往后的了,也不是从后往前的,

               而是根据可用的数量,选择枚举的顺序。

               这个模板使用了二进制优化;不懂以后慢慢理解;

               为什么不写了呢,因为女朋友刚刚跟我生气来着,浪费了好长时间呢,所以不写了!

               这告诉我们,哄好女朋友才是提高效率的唯一途径,没有的就算了。

                 #include<cstdio>
                 #include<algorithm>
                 #include<cstring>
                using namespace std;
                int Dp[60005];
    
                void ZeroOne( int cost ,int weight ,int sum )
                {
                    for( int i = sum ; i>= cost ; i-- )
                        Dp[i] = max( Dp[i], Dp[i - cost] + weight );
                }
                void Complete( int cost ,int weight,int sum )
                {
                    for( int i = cost ; i<= sum ; i++ )
                        Dp[i] = max( Dp[i], Dp[i-cost] + weight ); 
                }
                void Multi( int cost ,int weight, int count, int sum )
                {
                    if( cost * count >= sum )
                        Complete( cost , weight, sum );
                    else
                    {
                        int i=1; 
                        while( i < count )
                        {
                             ZeroOne( i*cost , i*weight, sum );
                             count -= i ;
                             i<<=1 ; 
                        }
                        ZeroOne( cost* count , weight * count , sum);
                    }
                }

        【进阶问题】:现在问题变了,物品还是原来的物品,现在我要问你能不能把在这个包装满,

               我不考虑物品的价值,只问你能不能装满。你说!能不能装满!

               其实都一样,就是个初始化的问题。。。

               不过今天做到BC一道题,题目要求取 K 个装满 V,这个。。还要思考一下行不行

              (其实就是下面要讲的这个 二维费用背包问题);

     <  二维费用 背了个包  >************************

         二维费用背包就是背包的升级版了。

        【基本描述】:给你n个物品,每个物品三个参数,包括两个费用参数,我们可以简单的理解为 va[ j ] 和 vb[ j ];

        【随便扯扯】:都说了是二维的了,此时的Dp当然就变成二维数组了,Dp[ U ][ V ] 表示这个包两种费用的最大承受值;

               那么对于一个物品,还是那样子,放还是不放,方放几个。。。也就是上面的几种DP方式;

               不同的是,对于状态转移的时候,要枚举两层了;

               

    void ZeroOne(int cost_u,int cost_v, int weight, int u, int v)
    {
        for(int i= u; i>=cost_u; i--)
            for(int j=v; j>=cost_v; j--)
                Dp[i][j] = max( Dp[i][j], Dp[i-cost_u][j-cost_v]+weight);
        
    }
    
    void Complete(int cost_u,int cost_v, int weight, int u, int v)
    {
        for(int i= cost_u; i<=u; i++)
            for(int j=cost_v; j<=v; j++)
                Dp[i][j] = max( Dp[i][j], Dp[i-cost_u][j-cost_v]+weight);
        
    }
    
    void Multi(int cost_u,int cost_v, int weight,int count,int u,int v)
    {
        if(cost_u * count >=u && cost_v * count >=v)
            Complete(cost_u, cost_v, weight, u, v);
        else
        {
            int i=1;
            while( i < count )
            {
                ZeroOne(cost_u*i, cost_v*i ,weight*i, u, v);
                count -= i;
                i<<=1;
            }
            ZeroOne(cost_u*count, cost_v*count, weight*count, u, v);
        }
    }

    明天继续.。。。。。。。。挂机ing

     <  分组 背了个包  >************************

         分组背包就是背包的升升级版了。

        【基本描述】:给你n个物品,每个物品三个参数,包括一个体积参数,一个价值参数,还有一个就是这个物品所在的组;

               但是都不重要,重要的是每一组里只能选一个物品出来用,或者不选(应该是每种物品只用一次);

        【随便扯扯】:二维的,此时的Dp当然就变成二维数组了,Dp[ K ][ V ] 表示前K组中,最大体积为V的状态获得的最大价值;

               首先这个Dp有三层循环。。。

               第一层枚举组,当前是第几组K。

               第二层枚举背包最大体积V。

               第三层枚举这一组中的所有物品 i。

               顺序是不能变的,因为对于当前的体积,枚举每一个物品取一个最优解,才能保证只会使用这其中的一个或0个

               同时枚举容量的时候倒着枚举,保证了正确性。

                      for(int k=1;k<=K;k++)
                          for(int j = V; j>=0; j--)
                              for(int i=1;i<=n;i++)
                              {
                                  if( j-v[i] < 0)
                                  continue;
                                  Dp[j] = max(Dp[j], Dp[j-v[i]]+weight[i]);
                              }

     <  依赖关系 背了个包  >************************

         依赖背包就是背包的升升升级版了。

        【基本描述】:给你n个物品,每个物品使用的时候满足某个物品必须使用的要求,

               可以理解为这些物品构成一个森林的关系结构,某个物品在取的时候必须满足其父亲节点已经取过。

        【随便扯扯】:这种问题的解法是在上一种解法的基础上改进过来的,也是一种分组的思维。有点强行分组的感觉。

               这里分组是这样的,对于一棵子树,我们考虑的是取几个物品,也就是0-n个。每一种取法之间都是

               互斥的,也就是说我们可以把所有的可能性都列出来,然后用分组背包来做。但是。。所有可能性

               会非常的多。也就是不行哒!

               然后我们又进一步优化这种思维,对于这棵子树,在所有的可能性中,其实有很多使可以舍弃掉的

               因为我的背包一共就V那么大,不同的取法中,取出来的体积大小只有V种,所以,我们可以先对

               相同V的取法中选取一个最优的取法,然后在这V个取法中再考虑分组背包去做

               (就是一组V件 物品取1件)。

               这种题目比较多变了,没有固定的模板,都是在前面的基础上,利用几种Dp方程组合出来的。

               正是由于变化多端,所以这类的题目比较多,难度多是中等题,这种思维是很多动态规划的基础。

               一般解法就是:

                  在考虑某个物品的时候,必须将其所有的子物品都考虑完,更新到Dp数组中,、

                  并且在合法的范围内,然后我们在来考虑这个整棵子树中的最优解(就是这一组中的最优解)

                  但是这并不是一种显示的分组做法,其更新的过程都隐藏在了DFS的过程中,一般不好看出来

                  是分组DP但是在思考的时候确实是用了分组背包的思维,这个还要自己理解一下。

                  hdoj1011 是一道这样的题,其实这是过渡到树形Dp的基础了。 

     <  泛化物品 背了个包  >************************

         泛化背包就是背包的升升……级版了。

        【基本描述】:泛化物品就是说,给你一些物品,但是每个物品的价值并不是固定的,根据你分配给这个物品的V

               而改变的,也就是其价值是一个在体积范围内的函数。

               其实其它的背包也可以看做是泛化的物品,0-1背包中我们可以看做是除了v[i ]其它价值都是0.。。。。

               最接近的一种就是分组背包中的了,对于一棵子树,分配给他的容量不同,最大的价值有可能就不同;

        【随便扯扯】:泛化的物品其实就是函数的复合,但不是普通的在x轴上的复合,是一种很好理解的抽象复合,

               即对于一个给定的容量,如果只有两个物品,那么我可以枚举分给每个物品的容量,取一个最大的。

               对每一种v都得到最大的就是将两个物品函数复合成了一个,就相当于替换掉了,然后就继续枚举其它

               的物品,最后会复合到一和函数中,然后最大值就是在定义域里寻找了。

  • 相关阅读:
    Oracle Core 学习笔记二 Transactions 和 Consistency 说明
    Oracle AUTO_SPACE_ADVISOR_JOB 说明
    Windows 下 ftp 上传文件 脚本
    Oracle 11g 中 Direct path reads 特性 说明
    Linux 使用 wget 下载 Oracle 软件说明
    Oracle 10g read by other session 等待 说明
    Oracle 11g RAC INS06006 Passwordless SSH connectivity not set up between the following node(s) 解决方法
    SecureCRT 工具 上传下载数据 与 ASCII、Xmodem、Ymodem 、Zmodem 说明
    Oracle RAC root.sh 报错 Timed out waiting for the CRS stack to start 解决方法
    Oracle RESETLOGS 和 NORESETLOGS 区别说明
  • 原文地址:https://www.cnblogs.com/by-1075324834/p/5449956.html
Copyright © 2020-2023  润新知