动态规划(DP)——背包问题

简介: 动态规划(DP)——背包问题

01背包

01背包问题描述

有一个背包,背包的容量为V,有n个物品,每个物品都有自己所占背包的空间大小和价值,每种物品只能用一次,求如何向背包中放入物品使得价值最大。

解题思路

问题一般可以分为:

1.状态表示 dp[i][j]:

(i为当前考虑是否放入第i个物品,j为当前背包的容量)

状态表示为一个集合,向背包中放入物品的所选方案的集合;

集合中的方案要满足,只从前i个物品中选,且中物品的总体积要小于等于j;

集合中的数据通常为最大值,最小值或者方案数,数量…

2.状态计算:

集合的划分

01背包问题,将集合划分为不包含i和包含i

不包含i——dp[i-1][j]

包含i——dp[i-1][j-vi]+wi

例题

题目来源:

洛谷 P1048 [NOIP2005 普及组] 采药

题目链接

题目描述:

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”如果你是辰辰,你能完成这个任务吗?

输入格式:

第一行有 22 个整数 TT(1 \le T \le 10001≤T≤1000)和 MM(1 \le M \le 1001≤M≤100),用一个空格隔开,TT 代表总共能够用来采药的时间,MM 代表山洞里的草药的数目。

接下来的 MM 行每行包括两个在 11 到 100100 之间(包括 11 和 100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式:

输出在规定的时间内可以采到的草药的最大总价值。

输入输出样例:

输入

70 3

71 100

69 1

1 2

输出

3

解题代码:

package luogu.dp.背包01;
import java.io.*;
import static java.lang.System.in;
public class P1048 {
    public static void main(String[] args) throws IOException {
        //快读
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
        in.nextToken();
        int t = (int) in.nval;//能用来采药的总时间
        in.nextToken();
        int m = (int) in.nval;//草药的数目(种类)
        int[] v = new int[m+1];//每个草药要花费的时间
        int[] w = new int[m+1];//每个草药的价值
        for ( int i=1; i<=m; i++ ) { //读入每个草药所需的时间和价值
            in.nextToken();
            v[i] = (int) in.nval;
            in.nextToken();
            w[i] = (int) in.nval;
        }
        int[][] f = new int[m+1][t+1]; //dp 存放所有方案的集合 集合中存放的数据为每种方案的价值
        for( int i=1; i<=m; i++ ) {  //循环尝试是否放入第i个草药
            for ( int j=0; j<=t; j++ ) { //循环背包的容量
                f[i][j] = f[i-1][j]; //前i个物品满足背包容量为j的方案的价值
                //当当前背包的容量大于第i个物品时,尝试放入第i个物品
                //与不放入进行比较,取其中的最大值
                if ( j>=v[i] ) f[i][j] = Math.max( f[i-1][j], f[i-1][j-v[i]]+w[i] );
            }
        }
        out.print(f[m][t]); //输出满足要求的最大价值
        out.flush();
    }
}

空间优化 (采用一维数组) :

package luogu.dp.背包01;
import java.io.*;
import static java.lang.System.in;
public class P1048 {
    public static void main(String[] args) throws IOException {
        //快读
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
        in.nextToken();
        int t = (int) in.nval;//能用来采药的总时间
        in.nextToken();
        int m = (int) in.nval;//草药的数目(种类)
        int[] v = new int[m+1];//每个草药要花费的时间
        int[] w = new int[m+1];//每个草药的价值
        for ( int i=1; i<=m; i++ ) { //读入每个草药所需的时间和价值
            in.nextToken();
            v[i] = (int) in.nval;
            in.nextToken();
            w[i] = (int) in.nval;
        }
        // 由于每次多出一个新物品都是在之前的基础上考虑是否放入新物品
        //所以可以采用一维数组从上向下滚动
        int[] f = new int[t+1]; 
        for( int i=1; i<=m; i++ ) {  //循环尝试是否放入第i个草药
            for ( int j=t; j>=v[i]; j-- ) { //循环背包的容量
                //因为从小到大遍历容量数组,前面的数值会影响后面的数值
                //所以反向遍历数组,背包容量小于当前物品所需容量时停止循环
                f[j] = Math.max( f[j], f[j-v[i]]+w[i] );
            }
        }
        out.print(f[t]); //输出满足要求的最大价值
        out.flush();
    }
}

完全背包

完全背包问题描述

有一个背包,背包的容量为V,有n个物品,每个物品都有自己所占背包的空间大小和价值,每种物品可以使用无数次,求如何向背包中放入物品使得价值最大。

解题思路

问题一般可以分为:

1.状态表示 dp[i][j]:

(i为当前考虑是否放入第i个物品,j为当前背包的容量)

状态表示为一个集合,向背包中放入物品的所选方案的集合;

集合中的方案要满足,只从前i个物品中选,且中物品的总体积要小于等于j;

集合中的数据通常为最大值,最小值或者方案数,数量…

2.状态计算:

集合的划分

完全背包问题,将集合划分为k份,每份包含k个第i个物品

1.先去掉k个物品i

2.求MAX,dp[i-1][j-kvi]
3.再加回k个物品i
dp[i-1][j-k
vi]+k*w[i]

例题

题目来源:

洛谷 P1616 疯狂的采药

题目链接

题目背景:

此题为纪念 LiYuxiang 而生。

题目描述:

LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”如果你是 LiYuxiang,你能完成这个任务吗?

此题和原题的不同点:

11. 每种草药可以无限制地疯狂采摘。

22. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入格式:

输入第一行有两个整数,分别代表总共能够用来采药的时间 tt 和代表山洞里的草药的数目 mm。

第 22 到第 (m + 1)(m+1) 行,每行两个整数,第 (i + 1)(i+1) 行的整数 a_i, b_ia i ,bi分别表示采摘第 ii 种草药的时间和该草药的价值。

输出格式:

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

输入输出样例:

输入

70 3

71 100

69 1

1 2

输出

140

说明/提示:

数据规模与约定

对于 30%30% 的数据,保证 m \le 10^3m≤10 3。

对于 100%100% 的数据,保证 1 \leq m \le 10^41≤m≤104 ,1 \leq t \leq 10^71≤t≤10 7 ,且 1 \leq m \times t \leq 10^71≤m×t≤10 7 ,1 \leq a_i, b_i \leq 10^41≤a i ,bi ≤104 。

解题代码:

package luogu.dp.完全背包;
import java.io.*;
public class P1616 {
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
        in.nextToken();
        int t = (int) in.nval;//能用来采药的总时间
        in.nextToken();
        int m = (int) in.nval;//草药的数目(种类)
        int[] v = new int[m+1];//每个草药要花费的时间
        int[] w = new int[m+1];//每个草药的价值
        for ( int i=1; i<=m; i++ ) {//读入每个草药所需的时间和价值
            in.nextToken();
            v[i] = (int) in.nval;
            in.nextToken();
            w[i] = (int) in.nval;
        }
        long[][] dp = new long[m+1][t+1];//dp 存放所有方案的集合 集合中存放的数据为每种方案的价值
        for ( int i=1; i<=m; i++ ) {//循环尝试是否放入第i个草药
            for ( int j=1; j<=t; j++ ) {//循环背包的容量
                for ( int k=0; k*v[i]<=j; k++ ) {//尝试可以放入草药i的个数
                    //从0开始
                    dp[i][j] = Math.max( dp[i][j], dp[i-1][j-v[i]*k]+w[i]*k );
                }
            }
        }
        out.print(dp[t]);
        out.flush();
    }
}

时间优化:

package luogu.dp.完全背包;
import java.io.*;
public class P1616 {
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
        in.nextToken();
        int t = (int) in.nval;//能用来采药的总时间
        in.nextToken();
        int m = (int) in.nval;//草药的数目(种类)
        int[] v = new int[m+1];//每个草药要花费的时间
        int[] w = new int[m+1];//每个草药的价值
        for ( int i=1; i<=m; i++ ) {//读入每个草药所需的时间和价值
            in.nextToken();
            v[i] = (int) in.nval;
            in.nextToken();
            w[i] = (int) in.nval;
        }
        long[][] dp = new long[m+1][t+1];//dp 存放所有方案的集合 集合中存放的数据为每种方案的价值
        for ( int i=1; i<=m; i++ ) {
            for ( int j=0; j<=t; j++ ) {
                //考虑要放入几个草药i时
                //可以在前面的基础上(本行)考虑是否要对放入的草药i的个数加一
                //这样子不用每次进行重新遍历循环
                dp[i][j] = dp[i-1][j];
                //当背包容量大于草药i的所需时间才进行考虑
                if ( j>=v[i] ) dp[i][j] = Math.max( dp[i][j], dp[i][j-v[i]]+w[i] );
            }
        }
        out.print(dp[t]);
        out.flush();
    }
}

空间优化:

package luogu.dp.完全背包;
import java.io.*;
public class P1616 {
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
        in.nextToken();
        int t = (int) in.nval;//能用来采药的总时间
        in.nextToken();
        int m = (int) in.nval;//草药的数目(种类)
        int[] v = new int[m+1];//每个草药要花费的时间
        int[] w = new int[m+1];//每个草药的价值
        for ( int i=1; i<=m; i++ ) {//读入每个草药所需的时间和价值
            in.nextToken();
            v[i] = (int) in.nval;
            in.nextToken();
            w[i] = (int) in.nval;
        }
        // 由于每次多出一个新物品都是在之前的基础上考虑是否放入新物品
        //所以可以采用一维数组从上向下滚动
        long[] dp = new long[t+1];
        for ( int i=1; i<=m; i++ ) {
            for ( int j=v[i]; j<=t; j++ ) {
                //因为无限个数,所以不会影响后面的
                //可以采用正序或倒序
                dp[j] = Math.max( dp[j], dp[j-v[i]]+w[i] );
            }
        }
        out.print(dp[t]);
        out.flush();
    }
}

多重背包

与完全背包的差别,物品有指明有多少个可以使用

朴素解法:

例题链接

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

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

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

输出最大价值。

输入格式

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

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式

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

数据范围

0<N,V≤100

0<vi,wi,si≤100

输入样例

4 5

1 2 3

2 4 1

3 4 3

4 5 2

输出样例:

10

package luogu.dp.多重背包;
import java.util.Scanner;
public class acwing多重背包I {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();//物品种数
        int m = sc.nextInt();//容量
        int[] v = new int[n+1];//体积
        int[] w = new int[n+1];//价值
        int[] s = new int[n+1];//数量
        for ( int i=1; i<=n; i++ ) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
            s[i] = sc.nextInt();
        }
        int[][] dp = new int[n+1][m+1];
        //多重背包转化为完全背包
        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++ ) { //物品的数量
                    //完全背包,从本行变化而来
                    dp[i][j] = Math.max( dp[i][j], dp[i-1][j-k*v[i]]+k*w[i] );
                }
            }
        }
        System.out.println( dp[n][m] );
    }
}

二进制优化:

题目链接

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

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

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

输出最大价值。

输入格式

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

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式

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

数据范围

0<N≤1000

0<V≤2000

0<vi,wi,si≤2000

提示:

本题考查多重背包的二进制优化方法。

输入样例

4 5

1 2 3

2 4 1

3 4 3

4 5 2

输出样例:

10

import java.util.ArrayList;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();//物品的种数
        int m = sc.nextInt();//背包容量
        ArrayList<Integer> v = new ArrayList<>();//体积
        ArrayList<Integer> w = new ArrayList<>();//物品的价值
        for ( int i=0; i<n; i++ ) {
            int a = sc.nextInt();//物品的体积
            int b = sc.nextInt();//物品的价值
            int c = sc.nextInt();//物品的数量
            //二进制优化
            int k = 1;
            while ( k<=c ) {
                v.add( a*k );
                w.add( b*k );
                c -= k;
                k *= 2;
            }
            //物品剩余的数量
            if ( c>0 ) {
                v.add( a*c );
                w.add( b*c );
            }
        }
        n = w.size();//更新物品种类的数量
        //接下去与01背包相同
        int[] dp = new int[m+1];
        for ( int i=0; i<n; i++ ) {
            //倒序遍历,防止前面影响后面
            for ( int j=m; j>=v.get(i); j-- ) {
                dp[j] = Math.max( dp[j], dp[j-v.get(i)]+w.get(i) );
            }
        }
        System.out.println( dp[m] );
    }

分组背包

分组背包:

有一个背包,背包的容量为V,有n组物品,每组的每个物品都有自己所占背包的空间大小和价值,每组物品可以选择其中的一个放入背包,求如何向背包中放入物品使得价值最大。

例题:

9. 分组背包问题

题目链接

有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8
package luogu.dp;
import java.io.*;
public class 分组背包 {
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
    public static void main(String[] args) throws IOException {
        int n = in();
        int v = in();
        int[][] vi = new int[n+1][];
        int[][] wi = new int[n+1][];
        int[] s = new int[n+1];
        for ( int i=1; i<=n; i++ ) {
            s[i] = in();//读入每组的个数
            vi[i] = new int[s[i]+1];
            wi[i] = new int[s[i]+1];
            for ( int j=1; j<=s[i]; j++ ) {
                vi[i][j] = in();
                wi[i][j] = in();
            }
        }
        int[] dp = new int[v+1];
        for ( int i=1; i<=n; i++ ) { //第i组
            for ( int j=v; j>=0; j-- ) { //倒序防止前面影响后面
                for ( int k=1; k<=s[i]; k++ ) {//第i组第j个
                    if ( j>=vi[i][k] ) {
                        dp[j] = Math.max( dp[j], dp[j-vi[i][k]]+wi[i][k] );
                    }
                }
            }
        }
        out.println(dp[v]);
        out.flush();
    }
    public static int in() throws IOException {
        in.nextToken();
        return (int) in.nval;
    }
}
相关文章
|
8月前
|
决策智能
【dp】背包问题
【dp】背包问题
330 2
动态规划(DP)——区间DP
动态规划(DP)——区间DP
动态规划之01背包问题和完全背包问题
动态规划之01背包问题和完全背包问题
|
10月前
|
人工智能
动态规划(DP)——线性DP
动态规划(DP)——线性DP
|
10月前
|
算法 C语言 C++
【动态规划】背包问题(01背包,完全背包)
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
103 0
|
11月前
动态规划——01背包问题、完全背包问题
动态规划——01背包问题、完全背包问题
69 0
|
存储
动态规划(DP)
动态规划(DP)
40 0
动态规划:完全背包问题
动态规划:完全背包问题
60 0
动态规划:多重背包问题
动态规划:多重背包问题
55 0
|
算法
【动态规划】使用到dp的题
【动态规划】使用到dp的题
92 0