zoukankan      html  css  js  c++  java
  • 背包问题

    01背包:
    每件物品都有它的价值和体积,你的背包有一定容量,如何能获取最大价值?

    第一行有2个整数分别表示容量和物品数(n)
    接下来n行每两个数个分别代表一个物体的体积和价值

    很显然,每种物品只能拿一件

    当然你也可以不拿

    如果拿(前提是有足够空间),就相当于背包少了v[i]的体积,多了c[i]的价值,

    不拿的话(没有空间)就是上一个物品占用当时最大的体积的价值

    一层循环枚举每一个物品

      二层循环枚举可用的最大体积(为什么这么说呢?,因为一个物体的体积如果是5,那么只剩2肯定装不下)

        如果能装下(c代表价值v代表枚举体积)

          f[i][v]=max(f[i-1][v],f[i-1][v-w[i]]+c[i]);

        否则

           f[i][v]=f[i-1][v];//只能跟前面一样

    注意,枚举的v是指最大占用v体积的情况下获得最大的价值

    v一定要倒着枚举(从最大体积开始到1结束)

    因为01背包只能选一个,你在选择v-w[i]时,f[i-1][v-w[i]]其中一定没有选过i,应该很容易理解吧.

    这时dalao们就发现问题了,若有5000件物品背包容量是100000,就爆空间了,这怎么办呢?

    我们可以想一个问题,如果用15的空间既可以获得10的价值也可以获得50的价值,那么你选哪个?

    都选50的吧

    那么获得10价值的方案还有用吗?

    显然没有用了

    所以我们只需要记录每一个容积所能获得的最大价值就好了,这样就可以压缩成为一维,从而舍弃第一维,只要容积那一维(只储存最大的价值)

     一层循环枚举每一个物品

      二层循环枚举可用的最大体积

        如果能装下

          f[v]=max(f[v],f[v-w[i]]+c[i]);

        否则//自己等于自己,当然没必要写这句

    完全背包:
    每件物品都有它的价值和体积但每件物品可以无限拿,你的背包有一定容量,如何能获取最大价值?

    第一行有2个整数分别表示容量和物品数(n)
    接下来n行每两个数个分别代表一个物体的体积和价值

    很显然,每种物品可以拿无数件

    当然你也可以不拿

    如果拿(前提是有足够空间),就相当于背包少了v[i]的体积,多了c[i]的价值,

    不拿的话(没有空间)就是上一个物品占用当时最大的体积的价值

    我们先讲一种便于理解的方法,即:
    加一重循环枚举个数k

    当然,依然不能超出背包限制

    状态转移方程f[v] = max{ f[v],f[ j - k*v[i]]+k* c[i]};

    这应该每位dalao和像我一样的蒟蒻都能明白吧

    然后明白了就可以上一种玄学一的点了

    一层循环枚举每一个物品

      二层循环枚举可用的最大体积

        如果能装下(c代表价值v代表枚举体积)

          f[v]=max(f[v],f[v-w[i]]+c[i]);

    注意,枚举的v是指最大占用v体积的情况下获得最大的价值

    v一定要正着枚举(从1开始到最大体积结束)

    因为完全背包可以无限选,你在选择v-w[i]时,他就有可能已经选过了.

    到这里有些像我一样的蒟蒻就不懂了

    我举一个生动形象的例子

    背包容量为5

    有1个物品,体积为2,价值为3(这么简单的例子不是因为我懒)

    从5开始呢?

    f[5]=f[5-2]+3;

    f[4]=f[4-2]+3;

    f[3]=f[3-2]+3;

    f[2]=f[2-2]+3;

    1和0不行

    可以看出,从5到2,关系分别是

    由3推5

    由2推4

    由1推3

    由0推2

    从一开始推出来的5开始没有被用过的再被利用

    从1开始呢?

    1和0不行

    f[2]=f[2-2]+3;

    f[3]=f[3-2]+3;

    f[4]=f[4-2]+3;

    f[5]=f[5-2]+3;

    可以看出,从5到2,关系分别是

    由0推2

    由1推3

    由2推4

    由3推5

    可以看出:

    由0推出的2又在推4的时候用到了,这就实现了完全背包

    大家应该可以发现我在写01背包和完全背包时有很多相同之处

    这是因为01背包是一种特殊的完全背包

    好啦,一篇博客结束啦

    emmm...怎么能结束了呢,我又来更新博客啦,

    二维费用背包,

    即有两个限制,比如,一件物品既有体积又有质量,你不能超出2者其中一个,求最大价值

    明白了01背包和完全背包之后,这个问题就简单许多

    假如一种限制也没有呢?

    代码应该是

        for(k=1; k<=c; k++) {
            ans=max(a[k]+ans,ans);
        }

    加一种限制呢?(即只有体积限制)

        for(k=1; k<=c; k++) {
            for(i=n; i>=v[k]; i--) {
                  a[i] = max(a[i-v[k]]+jz[k],a[i][j]);
            }
        }

    区别是什么?

    加了一层循环,又加了一维的空间

    由此推出

        for(k=1; k<=c; k++) {
            for(i=n; i>=v[k]; i--) {
                for(j=m; j>=w[k]; j--) {
                        a[i][j] = max(a[i-v[k]][j-w[k]]+jz[k],a[i][j]);
                }
            }
        }

    合情合理又简单.

    多重背包问题

    即一个物品可以拿指定的次数(这才是正常的日常问题)

    还是先考虑简单的情况

    转换01背包(硬转换)

    再加一层循环枚举个数,不能超过背包容量

        for(k=1; k<=c; k++) {
            for(i=n; i>=v[k]; i--) {
                for(j=1; j<=c[i]; j++) {
                        a[i] = max(a[i-j*w[k]]+j*jz[k],a[i]);
                }
            }
        }

    这样就连我这样的蒟蒻也明白吧

    接下来就要上一种更厉害的方法了

    3=1+2

    这个式子每个人都会吧

    转换2进制 11=1+10

    那若给你1,10,100,1000,10000.....

    这些二进制数,是不是能凑出所有的数呢?

    任何一个二进制数每一位要么0,要么1,所以都能凑出来

    举个栗子

    假如二进制第3位是1那么就加上1000,如果是0就不加

    好啦回归正题

    1,10,100,1000,10000.....

    转换10进制:  1,2,4,8....

    也就是说,我们不需要每个都枚举,假如有一个物品可以拿10个,那我们只需要将1,2,4,3这几种情况跑一遍01就好了

    可能还是有像我这样的蒟蒻看不懂,我来证明一下(看的别的dalao的证明方法)

    如果我们要凑的数n小于等于2^k(k是能拆最大的二进制数  以10为例,k就是2)转换二进制直接凑都明白吧

    如果n大于2^k怎么办呢?

    我们先想一个问题,假如你有2这个数,并且能凑出4这个数,那么是不是也能凑出4+2呢?

    如果要凑n,我们可以先凑n-2^k,n-2^k肯定能凑出,而2^k也能凑出,那么n就能凑出

    转换部分

        for(i=1; i<=n; i++) {
            scanf("%d%d%d",&w[i],&v[i],&s);
            for(j=1; j<=s; j*=2) {
                w[++k] = w[i]*j;
                v[k] = v[i]*j;
                s-=j;
            }
            if(0!=s) {
                w[++k] = w[i]*s;
                v[k] = v[i]*s;
            }
        }

    分组背包:

    有好多组物品,每组只能拿一个物品,使总价值最大

    其实这个很简单,只需要再加一层循环枚举组,使组内物品在不超出当前背包容积的情况下价值最大就好啦

        int zd2=0; 
        for(i=1; i<=zd; i++) {//枚举组
            for(j=m; j>=1; j--) {//枚举空间
                for(p=b[1].k ; p>=1; p--) {//枚举组内每一物品
                    if(w[b[i].bb[p]]<=j){//如果能装下
                        if(zd2<a[j-w[b[i].bb[p]]]+v[b[i].bb[p]])//找最大的
                            zd2=a[j-w[b[i].bb[p]]]+v[b[i].bb[p]];
                    } 
                        
                }
                a[j]=max(a[j],zd2);//在和原先的比较就好了
                zd2=0;
            }
        }

    注意:最好不要在找最大时就对a[]做手脚,否则可能出错(可以自己想下为什么)

    当然,大家看到这个代码肯定一脸懵,我在讲一下预处理

    这是定义的结构体

    struct po {
        int bb[1001];//存放组内每一个物品的序号
        int k;//有多少物品(组内)
    };
    po b[101]= {0};//

    输入部分:

    for(i=1; i<=n; i++) {
            scanf("%d%d%d",&w[i],&v[i],&zu);
            b[zu].bb[++b[zu].k] = i;//组内下一个物品编号
            if(zd<zu)//找出有多少组
                zd=zu;
        }

    更新完啦!!!

    时隔几个月,我又来更新了....

    至于树形背包,前提是先学会分组背包

    洛谷p2014选课为例

    设dp[x][i]为以x为根的子树,花i代价最大的价值,对于x的子树都是叶子节点的情况应该很好理解

    大家可以手动画一下图,这种情况很简单,但问题是一般情况我们要解决的并不是这种问题

    假设我们正在计算dp[x][k],树根的费用是m

    我们必须要选树根,因为树根不选是无法选儿子的

    那么我们只需要计算出所有子树中加起来费用是k - m的最大价值

  • 相关阅读:
    使用栈检测括号是否成对出现
    java多线程基本概述(十七)——原子性和易变性
    java多线程基本概述(十六)——捕获异常
    java多线程基本概述(十五)——DaemonThread
    java多线程基本概述(十四)——Thread内部类的几种写法
    java多线程基本概述(十三)——Executor
    java多线程基本概述(十二)——多线程与单例
    java多线程基本概述(十一)——ReadWriteLock
    java多线程基本概述(十)——Condition
    java多线程基本概述(九)——Lock(3)
  • 原文地址:https://www.cnblogs.com/lztzs/p/10798076.html
Copyright © 2011-2022 走看看