背包问题

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的最大价值

原文地址:https://www.cnblogs.com/lztzs/p/10798076.html