zoukankan      html  css  js  c++  java
  • 无可救药的背尼玛和包尼玛

    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都得到最大的就是将两个物品函数复合成了一个,就相当于替换掉了,然后就继续枚举其它

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

  • 相关阅读:
    Jenkins安装及配置
    数据库命令扩展
    常用的数据库命令
    如何使用NiFi等构建IIoT系统
    云计算之概念——IaaS、SaaS、PaaS、Daas
    emqx的一个配置参数
    利用jsoup抓取网页图片
    nohup使用
    jsoup的使用
    java知识点链接
  • 原文地址:https://www.cnblogs.com/by-1075324834/p/5449956.html
Copyright © 2011-2022 走看看