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

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

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

 

posted on 2016-05-01 02:22  bai_yan  阅读(190)  评论(0编辑  收藏  举报

导航