背包问题(se)

背包问题详解

背包问题核心思想为DP(Dynamic Programming),本文基于核心思路实现逐步推导.

最大价值简称:mv

01背包

例题描述:

有 N 件物品和一个容量为 M 的背包。第 i 件物品的重量是 Wi​,价值是 Di​。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

输入:

第一行:物品个数 N 和背包大小 M。

第二行至第 N+1 行:第 i 个物品的重量 Wi​ 和价值 Di​。

实验数据:

4 6 1 4 2 6 3 12 2 7

预测结果

23

题目地址:P2871 [USACO07DEC] Charm Bracelet S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

基础假设:dp[i][j] 表示在第i 个元素并且剩余容量为j时的最大价值

满足最优子结构和无后效性

基于基础假设,尝试得出2维dp

采用递推实现

物品\容量j=0j=1j=2j=3j=4j=5j=6
i=0,w=0,v=00000000
i=1,w=1,v=40444444
i=2,w=2,v=604610101010
i=3,w=3,v=1204612161822
i=4,w=2,v=704712161923

    注:     1.设置dp[i] 设置i=0 目的是防止dp[i-1] 越界

               2.表中数据表示此时的mv

打表代码:
    @Test
    void contextLoads() {
        int n = 4;
        int m = 6;
        int[] w = {0,1,2,3,2};
        int[] v = {0,4,6,12,7};

        int [][] dp = new int[5][7];

        for (int i = 1; i < 5; i++) {
            for (int j = 0; j < 7 ; j++) {
                if(j < w[i]){
                    dp[i][j] = dp[i-1][j];
                }else{
                    dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-w[i]] + v[i]);
                }
            }
        }

        for (int i = 0; i < 5; i++) {
            for (int i1 = 0; i1 < 7; i1++) {
                System.out.print(dp[i][i1]);
            }
            System.out.println();
        }
    }
解释表格得出过程:
  1. j=0或i=0时,表示容量为0,此时mv显然为0

  2. i=1且j=1时,容量j 等于w ,不拿mv=0,拿取mv=4,选择拿取

  3. i =1且j>1时,mv=4恒成立

  4. i=2且j=1时,j<w,无法拿取,mv = 4

  5. i=2且j=2时,j=w,满足拿取条件:不拿,mv = 4,拿取,mv = dp[1][2-2]+v[2]=0+6=6,故mv=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) =6

  6. i=2且j=3时,j>=w,满足拿取条件:不拿,mv=4,拿取mv =dp[1][3-2]+v[2]=4+6 =10,故mv = 10

  7. 之后情况依次递推

注:当i=1时,比较依然存在只是无意义

由过程推导得出:打表时的核心方程:当j > w[i],dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);

j<w[i],dp[i][j]=dp[i-1][j];

核心流程解释完毕

优化:

显然建表时,表大小约为(n+1)(m+1),可能造成MLE

理论优化思路:

i=n行的数据仅和i=n-1有关,考虑压缩数组(或者滚动数组),此时数组变为一维,核心方程变为j>=w[i],dp[j]=max(dp[j],dp[j-w[i]]+v[i]),j<w,dp[j]=dp[j],显然可以合并为 j=w[i],dp[j]=max(dp[j],dp[j-w[i]]+v[i])

代码优化思路:

直接将j>=w[i]提升至循环体内,减少比较次数

注意点:

对于j的遍历优化后需要从后往前,因为比较时需要dp[j-w[i]](也就是dp[i-1][j-w[i]]),从前向后会导致数据覆盖

优化后的测试代码:
   @Test
    void contextLoads2() {
        int n = 4;
        int m = 6;
        int[] w = {0,1,2,3,2};
        int[] v = {0,4,6,12,7};

        int[] dp = new int[7];

        for (int i = 1; i < 5; i++) {
            for (int j = 6; j >=w[i] ; j--) {
                    dp[j] = Math.max(dp[j],dp[j-w[i]] + v[i]);
            }
            for (int t = 0; t < dp.length; t++) {
                System.out.print(dp[t]);
            }
            System.out.println();
        }
    }

完全背包

题目地址:P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
朴素处理方式:

基于01背包,在i,j确定时,不断增加此物品的数量并向前查询,时间复杂度O(n^3).

测试代码:
@Test
    void contextLoads3() {
        int n = 3;
        int m = 70;
        int[] w = {71,69,1};
        int[] v = {100,1,2};

        int[] dp = new int[71];

        for (int i = 0; i < 3; i++) {
            for (int j =70; j >= w[i] ; j--) {
                for (int k = 0; k <= m/w[i]&&(j-k*w[i]>=0); k++) {
                    dp[j] = Math.max(dp[j],dp[j-k*w[i]] + k*v[i]);
                }
            }
            for (int t = 0; t < dp.length; t++) {
                System.out.print(dp[t]);
            }
            System.out.println();
            }
        System.out.println("ans"+dp[70]);
        }
优化:

O(n^3) 的复杂度是无法承受的,可以想象的是:如果在i,j为特值的某一点,若不拿,则dp[i][j]=dp[i-1][j],若拿取,则dp[i][j] = dp[i][j-w[i]] + v[i],所以状态转移方程为:dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]),压缩后dp[j]=max(dp[j],dp[j-w[i]]+v[i]),与01背包相同,但是遍历顺序从前向后(从前向后用到本层数据,从后向前用到上一层数据)

优化代码:
@Test
    void contextLoads4() {
        int n = 3;
        int m = 70;
        int[] w = {71,69,1};
        int[] v = {100,1,2};

        int[] dp = new int[71];

        for (int i = 0; i < 3; i++) {
            for (int j =w[i]; j <=70 ; j++) {
                    dp[j] = Math.max(dp[j],dp[j-w[i]] + v[i]);
            }
            for (int t = 0; t < dp.length; t++) {
                System.out.print(dp[t]);
            }
            System.out.println();
        }
        System.out.println("a
问题解释:

为何01背包和完全背包的继承关系不同(dp[i][...],dp[i-1][...])?,也就是为什么01背包继承上层,完全背包继承本层?

实际上二者本质相同,如果01背包中存在相同元素,继承上层等同于完全背包的继承本层;二者关键区别是物品数量和遍历顺序,所以01背包对于一个元素,不存在递进关系,而完全背包本层加数量时可以递进.

多重背包

多重背包可以转化为01背包问题,对于不同数量的元素,使用二进制转化,可以使得原本s种选择转为log(s)次选择,主要变动在于处理输入数据上,而非核心算法,故不详细说明

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lamooo19

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值