Acwing动态规划1——背包问题


常用模型:背包

背包模型区别状态方程
01背包每件物品最多只能用一次f[i][j] = max(f[i-1, j], f[i-1, j-v] + w)
完全背包每件物品可以用无限次f[i][j] = max(f[i-1, j], f[i, j-v] + w)
多重背包优化问题
分组背包n组物品,每组m种类型,只能从每组中选一个

1.0 1背包——每件物品最多只能用一次

问题:有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-anuMHBfx-1645181981227)(C:\Users\联想\AppData\Roaming\Typora\typora-user-images\image-20220218165438316.png)]

状态方程:f[i][j] = Math.max(f[i-1][j], f[i-1][j-v[i]] + w[i])

import java.io.IOException;
import java.util.Scanner;

/**
 * @author mys
 * @date 2022/2/1 20:29
 * 01背包
 * 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。
 * 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。
 */
public class p2_dp_01bag {
    static int N = 1010;

    public static void main(String[] args) throws IOException {
        int[] v = new int[N], w = new int[N];//v表示物品体积,w表示物品价值
        int[][] f = new int[N][N];//f表示状态 i物品个数 j背包容量

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入物品个数和背包容量:");
        int n = sc.nextInt();n表示物品个数,m表示背包容量
        int m = sc.nextInt();

        //输入所有物品
        System.out.println("请输入所有物品体积和价值:");
        for (int i = 1; i <= n; i ++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }

        //初始化时需要枚举所有状态f[0-n][0-m]
        //f[0][0-m] = 0:表示0件物品,其总体积不超过0-m,其最大价值是多少,默认为0,初始化时可以不写
        for (int i = 1; i <= n; i ++) {
            for (int j = 0; j <= m; j ++) {
                f[i][j] = f[i - 1][j];//不含i
                //包含i,注意判断情况,如果j<v[i]为空集,不满足条件
                if (j >= v[i]) {
                    f[i][j] = Math.max(f[i][j], f[i-1][j-v[i]] + w[i]);//先去掉i情况,再加上i的价值
                }
            }
        }
        System.out.println(f[n][m]);
    }
}

滚动数组:每次都是用固定的几个存储空间,将二维数组转为一维数组,节省存储空间,达到优化的效果。滚动数组只适用于计算最终结果,而不需要存储中间结果的场景

将状态f [i] [j]编程一维f[i]进行优化

为什么一维情况下枚举背包容量需要逆序?

在二维情况下,状态f [i] [j]是由上一轮i - 1的状态得来的,f [i] [j]与f[i - 1] [j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。

状态方程变为:f[j] = max(f[j], f[j - v[i]] + w[i]

最终代码:

import java.io.IOException;
import java.util.Scanner;

/**
 * @author mys
 * @date 2022/2/1 20:29
 * 01背包
 * 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。
 * 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。
 */
public class p2_dp_01bag2 {
    static int N = 1010;

    public static void main(String[] args) throws IOException {
        int[] v = new int[N], w = new int[N];//v表示物品体积,w表示物品价值
        int[] f = new int[N];//f表示状态 i物品个数 j背包容量

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入物品个数和背包容量:");
        int n = sc.nextInt();n表示物品个数,m表示背包容量
        int m = sc.nextInt();

        //输入所有物品
        System.out.println("请输入所有物品体积和价值:");
        for (int i = 1; i <= n; i ++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }

        for (int i = 1; i <= n; i ++) {
            for (int j = m; j >= v[i]; j --) {
                f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
            }
        }
        System.out.println(f[m]);
    }
}

2.完全背包——每件物品可以用无限次

问题:有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用,第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-benqSds6-1645181981228)(C:\Users\联想\AppData\Roaming\Typora\typora-user-images\image-20220218182912947.png)]

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;

/**
 * @author mys
 * @date 2022/2/18 17:18
 * 有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
 * 第 i 种物品的体积是 vi,价值是 wi。
 * 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
 *
 * 输入:
 * 4 5
 * 1 2
 * 2 4
 * 3 4
 * 4 5
 * 输出:10
 */
public class p3_dp_completeBag {
    static int N = 1010;

    public static void main(String[] args) throws IOException {
        int[] v = new int[N], w = new int[N];//v:体积 w:价值
        int[][] f = new int[N][N];//f:状态

        Scanner sc = new Scanner(System.in);
        //输入物品个数和背包数量
        int n = sc.nextInt();
        int m = sc.nextInt();
        //输入所有物品体积和价值
        for (int i = 1; i <= n; i ++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }

        //完全背包状态方程 i物品个数 j背包容量
        for (int i = 1; i <= n; i ++) {
            for (int j = 0; j <= m; j ++) {
                //k * v[i] <= j:如果有k个物品i,保证这k个物品的体积小于背包容量
                for (int k = 0; k * v[i] <= j; k ++) {
                    f[i][j] = Math.max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
                }
            }
        }
        System.out.println(f[n][m]);
    }
}

优化问题

1、优化k层循环

优化后核心代码:

//完全背包
for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= m; j++) {
        f[i][j] = f[i - 1][j];
        if (j >= v[i]) {
            f[i][j] = Math.max(f[i][j], f[i][j - v[i]] + w[i]);
        }
    }
}
//对比01背包
for (int i = 1; i <= n; i ++) {
            for (int j = 0; j <= m; j ++) {
                f[i][j] = f[i - 1][j];//包含i
                //不包含i,注意判断情况,如果j<v[i]为空集,不满足条件
                if (j >= v[i]) {
                    f[i][j] = Math.max(f[i][j], f[i-1][j-v[i]] + w[i]);//先去掉i情况,再加上i的价值
                }
            }
        }

2、二维转一维

优化后核心代码:

//完全背包
for (int i = 1; i <= n; i++) {
    for (int j = v[i]; j <= m; j++) {
        f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
    }
}
//对比01背包
for (int i = 1; i <= n; i ++) {
            for (int j = m; j >= v[i]; j --) { //注意:逆序
                f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
            }
        }

完全背包一维优化后与01背包非常相似

f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包

f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题

最终代码:

import java.io.IOException;
import java.util.Scanner;

/**
 * @author mys
 * @date 2022/2/18 17:18
 * 优化 二维->一维
 */
public class p3_dp_completeBag3 {
    static int N = 1010;

    public static void main(String[] args) throws IOException {
        int[] v = new int[N], w = new int[N];//v:体积 w:价值
        int[] f = new int[N];//f:状态

        Scanner sc = new Scanner(System.in);
        //输入物品个数和背包数量
        int n = sc.nextInt();
        int m = sc.nextInt();
        //输入所有物品体积和价值
        for (int i = 1; i <= n; i++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }

        //完全背包状态方程 i物品个数 j背包容量
        for (int i = 1; i <= n; i++) {
            for (int j = v[i]; j <= m; j++) {
                f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
            }
        }
        System.out.println(f[m]);
    }
}

3.多重背包——优化问题

问题:有 N 种物品和一个容量是 V 的背包,第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大,输出最大价值。

数据范围:

第一种:0<N,V≤1000<N,V≤100, 0<vi,wi,si≤100

第二种:

0<N≤10000<N≤1000
0<V≤20000<V≤2000
0<vi,wi,si≤2000

在这里插入图片描述
第一种:暴力解法

import java.util.Scanner;

/**
 * @author mys
 * @date 2022/2/18 20:23
 * 有 N 种物品和一个容量是 V 的背包。
 * 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
 * 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
 *
 * 输入:
  4 5
  1 2 3
  2 4 1
  3 4 3
  4 5 2
 * 输出:10
 */
public class p4_dp_multipleBag {
    public static int N = 110;
    public static void main(String[] args) {
        int[] v = new int[N], w = new int[N], s = new int[N];//体积 价值
        int[][] f = new int[N][N];//状态

        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();//物品个数
        int m = sc.nextInt();//背包容量

        //输入物品信息
        for (int i = 1; i <= n; i ++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
            s[i] = sc.nextInt();
        }

        for (int i = 1; i <= n; i ++) {
            for (int j = 0; j <= m; j ++) {
                for (int k = 0; k <= s[i] && k * v[i] <= j; k ++) {
                    //朴素状态方程
                    f[i][j] = Math.max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
                }
            }
        }
        System.out.println(f[n][m]);
    }
}

第二种:二进制优化

最终代码:

import java.util.Scanner;

public class p4_dp_multipleBag2 {
    //N >= 2000 * log2000 保险取值取25000  2000种物品,每种物品logS件
    public static int N = 25000, M = 2010;
    public static void main(String[] args) {
        int[] v = new int[N], w = new int[N];//体积 价值
        int[] f = new int[N];//状态

        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();//物品种数
        int m = sc.nextInt();//背包容量

        int count = 0;//存储新的物品
        for (int i = 1; i <= n; i ++) {
            //输入当前物品的信息
            int volume = sc.nextInt();//体积
            int value = sc.nextInt();//价值
            int s = sc.nextInt();//个数

            //一组二进制数:1 2 4 8 ... 2^k C
            int k = 1;//从1开始凑和
            //用一组二进制数凑和,只要总和<=S就继续凑和,当总和要>S时就停止,最后如果有剩余用C来凑
            while (k <= s) {
                count ++;
                v[count] = volume * k;//k个物品的体积打包放一起
                w[count] = value * k;//k个物品的价值打包放一起
                s -= k;//S凑完之后减去
                k *= 2;//k变为下一个二进制数
            }
            //说明还剩下一些,相当于数组中的C 拼凑到一起补上
            if (s > 0) {
                count ++;
                v[count] = volume * s;
                w[count] = value * s;
            }
        }
        n = count;//更新n
        for (int i = 1; i <= n; i ++) {
            for (int j = m; j >= v[i]; j --) {
                f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
            }
        }
        System.out.println(f[m]);
    }
}

4.分组背包

n组物品,每组m种类型,只能从每组中选一个

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l5JXVS6R-1645869065339)(C:\Users\联想\AppData\Roaming\Typora\typora-user-images\image-20220226174808528.png)]

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i是组号,j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式

第一行有两个整数 N,VN,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 NN 组数据:

  • 每组数据第一行有一个整数 Si,表示第 ii 个物品组的物品数量;
  • 每组数据接下来有 Si 行,每行有两个整数 vij,wijvij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000<N,V≤100
0<Si≤1000<Si≤100
0<vij,wij≤100

import java.util.Scanner;

/**
 * @author mys
 * @date 2022/2/26 17:19
 *
 * 输入:
  3 5
  2
  1 2
  2 4
  1
  3 4
  1
  4 5
 * 输出:8
 */
public class p5_dp_groupBag {
    static int N = 110;
    public static void main(String[] args) {
        int[][] v = new int[N][N], w = new int[N][N];//v体积 w价值
        int[] s = new int[N], f = new int[N];//s[i]:第i个物品的数量 f:状态

        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();//物品个数
        int m = sc.nextInt();//背包容量

        //输入物品信息
        for (int i = 1; i <= n; i ++) {
            s[i] = sc.nextInt();
            for (int j = 0; j < s[i]; j ++) {
                v[i][j] = sc.nextInt();
                w[i][j] = sc.nextInt();
            }
        }

        //遍历
        //用的上层状态,由大到小遍历体积
        //用的本层状态,由小到大遍历体积
        for (int i = 1; i <= n; i ++) {//物品数
            for (int j = m; j >= 0; j --) {//背包容量
                for (int k = 0; k < s[i]; k ++) {//k个i物品
                    if (v[i][k] <= j) {
                        f[j] = Math.max(f[j], f[j - v[i][k]] + w[i][k]);
                    }
                }
            }
        }
        System.out.println(f[m]);
    }

}

2、线性DP

3、 区间DP

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值