背包专题详解,实用有效

  • Post author:
  • Post category:其他




”明月如霜,好风如水,清景无限 “

  • 背包问题的经典程度,不言而喻。今天文远就带大家梳理一遍。主要是01背包,完全背包,多重背包和分组背包问题。关于空间压缩可以见:

    空间压缩

  • 关键点:一般用dp[i][j]表示只用前i个物品在背包空间为j的情况下物品的最大价值。






* 01背包

  • 描述:n个物品,背包容量m,物品容量v[i],价值w[i]。物品只能选0次或者1次。

  • 状态分析很简单,即


dp[i][j] = max(dp[i-1][j] , dp[i – 1][j – v[i]] + w[i]);

但要注意第i个物品体积v[i]得小于等于j。

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

  • 一维代码

    :分析二维代码发现,如果j正向循环,则dp[i – 1][j – v[i]]会被错误替换为dp[i][j – v[i]]。即dp[j – v[i]]表示的是后者。详细见:

    空间压缩

    。因此j倒序遍历:
for(int i = 1 ; i <= n ; ++i){
    for(int  j = m ; j >= v[i] ; --j){
            dp[j] = max(dp[j] , dp[j - v[i]] + w[i]);
    }
}






* 完全背包

  • 描述:n个物品,背包容量m,物品容量v[i],价值w[i]。物品只能选0次或者无限次。

  • 状态分析很简单,即


dp[i][j] = max(dp[i-1][j] , dp[i – 1][j – v[i]] + w[i] , dp[i – 1][j – 2

v[i]] +2 * w[i] , dp[i – 1][j – k * v[i]] + k * w[i]);

但要注意k个i个物品体积k * v[i]得小于等于j。

for(int i =1 ; i <= n ; ++i){
    for(int j = 1 ; j <= m ; ++j){
        for(int k = 0 ; k * v[i] <= j ; ++k)
        dp[i][j] = max(dp[i][j] , dp[i - 1][j - k * v[i]] + k * w[i]);
    }
}


max(dp[i – 1][j – v[i]] + w[i] , dp[i – 1][j – 2

v[i]] +2 * w[i] , dp[i – 1][j – k * v[i]] + k * w[i]);

同样注意第i个物品体积v[i]得小于等于j。

for(int i =1 ; i <= n ; ++i){
    for(int j = 1 ; j <= m ; ++j){
        dp[i][j] = dp[i - 1][j];
        if(j >= v[i])
            dp[i][j] = max(dp[i][j] , dp[i][j - v[i]] + w[i]);
    }
}

  • 一维代码

    :类比上述01背包的等价替换,即状态转移方程正好是i,直接去掉物品维(i),则刚刚好。因为j正向遍历时,i 都是当前物品。
for(int i =1 ; i <= n ; ++i){
    for(int j = v[i] ; j <= m ; ++j){
            dp[j] = max(dp[j] , dp[j - v[i]] + w[i]);
    }
}






* 多重背包

  • 描述:n个物品,背包容量m,物品容量v[i],价值w[i]。物品只能选0次或者最多s[i]个。

  • 状态分析:


k = 0 ++ (k <= s[i] && k * v[i] <= m)

dp[i][j] = max(dp[i][j] , dp[i – 1][j – k * v[i]] + k + w[i]);

类比完全背包的暴力做法,但是要加条件k <= s[i]

for(int i = 1 ; i <= n ; ++i){
    for(int j = 1 ;  j <= m ;++j){
        for(int k = 0 ; k <= s[i] && j >= k * v[i] ; ++k){
            dp[i][j] = max(dp[i][j] ,dp[i - 1][j - k * v[i]] + k * w[i]);
        }
    }
}
  • 降维代码:此处很狗在于,没有完全背包的优化:因为发现dp[i][j – v[i]] + w[i] 不能刚好替换:

*

max(dp[i – 1][j – v[i]] + w[i] , dp[i – 1][j – 2

v[i]] +2 * w[i] , dp[i – 1][j – k * v[i]] + k * w[i]);

多了一个:dp[i -1][j – (s[i] + 1)*v[i] ] + s[i] * w[i]。如下图:

但是我们可以将其转化为01背包问题,即将从0到s遍历换为0到log s。此为二进制优化。其实就是物品打包。

将 n * s的物品变为 n * log s个物品

,物品的v和w都要变化。这是因为 log s 个数二进制能够表示s个数:

///二进制优化,将限制的 s[i] 转换为 log s , 即物品打包,变为 0 1背包
//主要时预处理v和w数组
    int idx = 0;
    for(int i = 1 ; i <= n ; ++i){
        int a , b , s;
        scanf("%d%d%d" , &a , &b ,&s);    
        for(int k = 1; k <= s ; k *= 2){
            ++idx;
            v[idx] = a * k;
            w[idx] = b * k;
            s -= k;
        }
        if(s > 0){二进制剩余补全
            ++idx;
            v[idx] = a * s;
            w[idx] = b * s;
        }
    }
    n = idx;///即 n 变为了 n * log s 
    /然后按照 01背包处理






* 分组背包

描述:n组物品,背包容量m,i组第j物品容量v[i][j],价值w[i][j]。物品只能选0次或者组中1个。

  • 关键点:是选i组k个物品中的哪一个,或者i组不选。


  • 二维代码

    :

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 110;
int n , m;
int v[N][N] , w[N][N] , s[N];
int dp[N][N];

int main(){
    scanf("%d%d" , &n , &m);
    for(int i = 1; i <= n ; ++i ){
        scanf("%d" , &s[i]);
        for(int j = 1 ; j <= s[i] ; ++j)
            scanf("%d%d" , &v[i][j] , &w[i][j]);
    }
    
    // for(int i = 1 ; i <= n ; ++i){
    //     for(int j = m ; j > 0  ; --j){注意此处,无法使用 j >= v[i][k]
    //         for(int k = 1 ; k <= s[i] ; ++k).枚举物品序号????
    //             if(j >= v[i][k])
    //                 dp[j] = max(dp[j] , dp[j - v[i][k]] + w[i][k]);
    //     }
    // }
    for(int i = 1 ; i <= n ; ++i){
        for(int j = 1 ; j <= m  ; ++j){注意此处,无法使用 j >= v[i][k]
        	dp[i][j] = dp[i - 1][j]; /为什么要放在这里呢,即第 i 组不选
            for(int k = 1 ; k <= s[i] ; ++k){.枚举物品序号????
                //dp[i][j] = dp[i -1][j];
                if(j >= v[i][k])
                    dp[i][j] = max(dp[i][j] , dp[i - 1][j - v[i][k]] + w[i][k]);
            }
        }
    }
    printf("%d\n",dp[n][m]);
    return 0;
}
    for(int i = 1 ; i <= n ; ++i){
        for(int j = m ; j > 0  ; --j){注意此处,无法使用 j >= v[i][k]
            for(int k = 1 ; k <= s[i] ; ++k).枚举物品序号????
                if(j >= v[i][k])
                    dp[j] = max(dp[j] , dp[j - v[i][k]] + w[i][k]);
        }
    }

源码可点击

阅读原文

,记得点赞关注。



END

公众号:文远的学习笔记

作者:不爱跑马的影迷不是好程序猿

   喜欢的话请关注点赞👇 👇👇 👇                     

在这里插入图片描述

壹句: 他山石



版权声明:本文为john_ashley原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。