我是靠谱客的博主 忧虑猫咪,最近开发中收集的这篇文章主要介绍动态规划(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;
    }
}

最后

以上就是忧虑猫咪为你收集整理的动态规划(DP)——背包问题的全部内容,希望文章能够帮你解决动态规划(DP)——背包问题所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(69)

评论列表共有 0 条评论

立即
投稿
返回
顶部