背包九讲

 

 

01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i件物品的体积是 vi,价值是 wi。

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

输入格式

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

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

输出格式

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

数据范围

0<N,V≤10000
0<vi,wi≤10000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

 

import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner in=new Scanner(System.in);
        int n=in.nextInt();
        int v=in.nextInt();
        int[] arr=new int[v+1];//arr[30]为30大小的背包最大的价值是多少
        for(int i=0;i<n;i++) {
        	int vi=in.nextInt();
        	int wi=in.nextInt();
        	if(vi>v)
        		continue;//如果当前物品超过背包最大重量
        	for(int j=v;j>=vi;j--) {
        		arr[j]=Math.max(arr[j],arr[j-vi]+wi);//判断当前背包价值更高,还是当前背包的重量减去当前需要放的物品的重量的那个重量的最大价值加上当前物品价值更高,最高的那个即是当前背包存放至当前物品,大小的最大价值
        	}
        }
        System.out.println(arr[v]);
    }
}

分析:基本背包问题也就是记忆型动态规划问题——从低往高求,如:5大小的背包是从3大小背包加上2重量物质或者是4大小背包来获得的答案。

完全背包问题

有 N种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i种物品的体积是 vi,价值是 wi。

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

输入格式

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

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

输出格式

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

数据范围

0<N,V≤10000
0<vi,wi≤10000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10
import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner in=new Scanner(System.in);
        int n=in.nextInt();
        int v=in.nextInt();
        int[] arr=new int[v+1];
        for(int i=0;i<n;i++) {
        	int vi=in.nextInt();
        	int wi=in.nextInt();
        	for(int j=vi;j<=v;j++) {
        		arr[j]=Math.max(arr[j],arr[j-vi]+wi);
        	}
        }
        System.out.println(arr[v]);
    }
}

无限种的情况下,则需要从最大重量往低的来,但是总思路是不变的——从当只有一个物品的时候,到两个,三个,一直到所有——每次进入一个物品,则需要重新考虑每个重量的情况下的最大价值,考虑方式也一样——如果加上当前物品——当前重量减去当前物品重量的那个重量的最大价值加上当前物品价值的总价值,是否比当前价值更高

 

多重背包问题

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

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

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

输入格式

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

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

输出格式

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

数据范围

0<N≤10000
0<V≤200000
0<vi,wi,si≤200000

提示

本题考查多重背包的单调队列优化方法。

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10
import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner in=new Scanner(System.in);
        int n=in.nextInt();
        int v=in.nextInt();
        int[] arr=new int[v+1];
        for(int i=0;i<n;i++) {
        	int vi=in.nextInt();
        	int wi=in.nextInt();
        	int ki=in.nextInt();
        	for(int j=v;j>=0;j--) {
        		for(int ii=1;ii<=ki&&ii*vi<=j;ii++) {
        			arr[j]=Math.max(arr[j],arr[j-vi*ii]+wi*ii);
        		}
        	}
        }
        System.out.println(arr[v]);
    }
}

常规方式,按照基本的背包思路求解,显然,在很大数据的时候是会超时的。

于是可以产生一个新思路——为k数量限制的物品,是否可以把这个物品看做是k个不同的物品,然后类似01背包问题来对其进行求解——得出的结论是,可行,但是时间复杂度依旧超时

于是可以得出进一步的优化——k物品的这个k不必拆分成全为1的数量

如:13=1+1+1+1+1+1+1+1+1+1+1+1+1,然后对每个1进行取舍,这显然会超时

那么可否改成13=1+2+4+6

然后对1,2,4,6分别进行01取舍

会发现,无论这个物品选几个

1=1

2=2

3=1+2

4=4

5=4+1

6=2+4

7=6+1

......

都可以通过组数量为1,2,4,6这些物品进行01取舍得到

于是,小范围的n复杂度就变成了logn复杂度了

上代码:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;

public class Main{
	public static void main(String[] args)throws Exception{
		Scanner in=new Scanner(System.in);
		int n=in.nextInt();
		int v=in.nextInt();
		int[] arr=new int[v+1];
		
		for(int i=0;i<n;i++) {
			int vi=in.nextInt();
			int wi=in.nextInt();
			int si=in.nextInt();
			ArrayList<Integer> al=new ArrayList<Integer>();
			//把si拆分成若干2次方数据,以及最后一个数
			for(int x=1;;x*=2) {
				if(si<x) {
					al.add(si);
					break;
				}
				al.add(x);
				si-=x;
			}
			
			for(int x:al) {
				for(int j=v;j>=x*vi;j--) {
					arr[j]=Math.max(arr[j],arr[j-x*vi]+x*wi);
				}
			}
		}
		System.out.println(arr[v]);
	}
}

更高效的方法有关于单调队列问题,正在研究学习中

 

 

混合背包问题:

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

物品一共有三类:

  • 第一类物品只能用1次(01背包);
  • 第二类物品可以用无限次(完全背包);
  • 第三类物品最多只能用 si次(多重背包);

每种体积是 vi,价值是 wi。

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

输入格式

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

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

  • si=−1si=−1 表示第 i 种物品只能用1次;
  • si=0si=0 表示第 i 种物品可以用无限次;
  • si>0si>0 表示第 i 种物品可以使用 sisi 次;

输出格式

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

数据范围

0<N,V≤10000
0<vi,wi≤10000
−1≤si≤1000

输入样例

4 5
1 2 -1
2 4 1
3 4 0
4 5 2

输出样例:

8

 

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;

public class Main{
	public static void main(String[] args)throws Exception{
		Scanner in=new Scanner(System.in);
		int n=in.nextInt();
		int v=in.nextInt();
		int[] arr=new int[v+1];
		
		for(int i=0;i<n;i++) {
			int vi=in.nextInt();
			int wi=in.nextInt();
			int si=in.nextInt();
			
			if(si==-1) {
				for(int j=v;j>=vi;j--) {
					arr[j]=Math.max(arr[j],arr[j-vi]+wi);
				}
			}
			else if(si==0) {
				for(int j=vi;j<=v;j++) {
					arr[j]=Math.max(arr[j],arr[j-vi]+wi);
				}
			}
			else {
			ArrayList<Integer> al=new ArrayList<Integer>();
			//把si拆分成若干2次方数据,以及最后一个数
			for(int x=1;;x*=2) {
				if(si<x) {
					al.add(si);
					break;
				}
				al.add(x);
				si-=x;
			}
			
			for(int x:al) {
				for(int j=v;j>=x*vi;j--) {
					arr[j]=Math.max(arr[j],arr[j-x*vi]+x*wi);
				}
			}
			}
		}
		System.out.println(arr[v]);
	}
}

 

也就是01背包完全背包和多重背包组合而已,加三个if就行了,不作解析

 

二维费用的背包问题:

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。

每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。

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

输入格式

第一行两个整数,N,V, M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。

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

输出格式

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

数据范围

0<N≤10000
0<V,M≤1000
0<vi,mi≤1000
0<wi≤10000

输入样例

4 5 6
1 2 3
2 4 4
3 4 5
4 5 6

输出样例:

8

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;

public class Main{
	public static void main(String[] args)throws Exception{
		Scanner in=new Scanner(System.in);
		int n=in.nextInt();
		int v=in.nextInt();
		int m=in.nextInt();
		int[][] arr=new int[v+1][m+1];
		
		for(int i=0;i<n;i++) {
			int vi=in.nextInt();
			int mi=in.nextInt();
			int wi=in.nextInt();
			
			for(int j=v;j>=vi;j--) {
				for(int k=m;k>=mi;k--) {
					arr[j][k]=Math.max(arr[j][k],arr[j-vi][k-mi]+wi);
				}
			}
		}
		System.out.println(arr[v][m]);
	}
}

和01背包一样,仅仅是把一维变成了二维而已,思路公式也都完全一样,不做分析了。

 

分组背包问题:

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

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

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

输出最大价值。

输入格式

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

接下来有 N 组数据:

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

输出格式

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

数据范围

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

输入样例

3 5
2
1 2
2 4
1
3 4
1
4 5

输出样例:

8

 

import java.util.Scanner;

public class Main{
	public static void main(String[] args)throws Exception{
		Scanner in=new Scanner(System.in);
		int n=in.nextInt();
		int v=in.nextInt();
		int[] arr=new int[v+1];
		
		for(int i=0;i<n;i++) {
			int s=in.nextInt();
			int[] vi=new int[s];
			int[] wi=new int[s];
			for(int j=0;j<s;j++) {
				vi[j]=in.nextInt();
				wi[j]=in.nextInt();
			}
			for(int k=v;k>=0;k--) {
				for(int j=0;j<s;j++) {
					if(k>=vi[j]) {
						arr[k]=Math.max(arr[k],arr[k-vi[j]]+wi[j]);
					}
				}
			}
		}
		System.out.println(arr[v]);
	}
}

 和前面的一样思路,仅仅是把每次背包大小循环的时候,每一个空间里,进行组数判断

如:判断第五组物品背包大小30的时候,在30的这个循环内部,增加判断:那些物品里,哪一个放进去的时候这个30的背包总价值最高,就放这个物品。

 

背包问题求方案数

 

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i件物品的体积是 vi,价值是 wi。

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

输出 最优选法的方案数。注意答案可能很大,请输出答案模 10^9+7 的结果。

输入格式

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

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

输出格式

输出一个整数,表示 方案数 模 10^9+7 的结果。

数据范围

0<N,V≤10000
0<vi,wi≤10000

输入样例

4 5
1 2
2 4
3 4
4 6

输出样例:

2

 

import java.util.ArrayList;
import java.util.Scanner;

public class Main{
	public static void main(String[] args)throws Exception{
		Scanner in=new Scanner(System.in);
		int n=in.nextInt();
		int v=in.nextInt();
		int[] arr=new int[v+1];//arr[i]存放当体积为i的时候,最大的价值数
		int[] an=new int[v+1];//an[i]存放当体积为i,当前价值最高的时候的方案数
		for(int i=0;i<v+1;i++)//首先给每个值赋值为1,以便接下来很好的运算
			an[i]=1;
		for(int i=0;i<n;i++) {//和零一背包问题一样
			int vi=in.nextInt();
			int wi=in.nextInt();
			for(int ii=v;ii>=vi;ii--){
				if(arr[ii-vi]+wi==arr[ii]) 
					an[ii]=(an[ii]+an[ii-vi])%1000000007;
//当放不放入都可行的时候,表明放不放入的方案书都需要计算当中,an[ii-vi]为那时候体积的方案数,也就是放入情况下的方案数,而an[ii]为不放入的方案数,累加即为当前方案数
				else if(arr[ii-vi]+wi>arr[ii]) {
//如果放入物品价值更高,则表明之前计算的an[ii]的方案数是需要被淘汰的,新值即为an[ii-vi]也就是放入这个物品情况下的方案数
					arr[ii]=arr[ii-vi]+wi;
					an[ii]=an[ii-vi];
				}
			}
		}
		System.out.println(an[v]);
	}
}

这道题也就是01背包,只是多了一个计数问题

计数问题思路在于:an[i]记录,当arr[i](当计算到当前物品时,i体积的大小的情况下,最高的价值)在这个最高价值的情况下的方案数,,而,有两种情况下才会出现数量的变更,

第一为当前价值不是最高价值——计入新的物品的时候,价值更高,那么当前计算的价值就需要舍弃,计入新的方案数自然就是继承an[i-vi]的方案数了。

第二为发现一个新的方案,也可以达到最高价值,把这个方案的方案数加进去即可,依旧是an[i-vi]大小。

背包问题求具体方案

 

有 N件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

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

输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N。

输入格式

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

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

输出格式

输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。

物品编号范围是 1…N。

数据范围

0<N,V≤10000
0<vi,wi≤10000

输入样例

4 5
1 2
2 4
3 4
4 6

输出样例:

1 4
import java.util.ArrayList;
import java.util.Scanner;

public class Main{
	public static void main(String[] args)throws Exception{
		Scanner in=new Scanner(System.in);
		int n=in.nextInt();
		int v=in.nextInt();
		int[][] arr=new int[n+2][v+1];
		int[] vv=new int[n+1];
		int[] ww=new int[n+1];
		for(int i=1;i<=n;i++) {
			vv[i]=in.nextInt();
			ww[i]=in.nextInt();
		}
		for(int i=n;i>=1;i--) {
			for(int ii=0;ii<=v;ii++){
				arr[i][ii]=arr[i+1][ii];
				if(ii>=vv[i])
					arr[i][ii]=Math.max(arr[i+1][ii],arr[i+1][ii-vv[i]]+ww[i]);
			}
		}
//这里就是用典型的最原始的01背包问题公式求解,唯一不同的地方是存放物品从后往前
//原因是需要保证返回去逆推物品清单的时候,先从最小的物品开始判断
//		System.out.println(arr[1][v]);
		ArrayList<Integer> al=new ArrayList<Integer>();//存放物品清单
		int pi=v;
		for(int i=1;i<=n;i++) {
			if(pi>=vv[i])
				if(arr[i+1][pi-vv[i]]+ww[i]==arr[i][pi]) {
//只要这个物品有存放记录,那就取出这个物品
					pi-=vv[i];
					al.add(i);
				}
		}
		for(int i:al)
			System.out.print(i+" ");
	}
}

同样是01背包问题求解

思路为,先当做01背包问题求出值以后,根据值和相关的记录返回来推导这个值是用哪些物品得出的结果,而考虑到需要输出字典序最小的方案,自然是能取1的时候就取1,能取2的时候就取2的这种方法。

因为不仅需要根据值,并且还需要根据相关记录来推导出物品清单,所以这个时候就需要用到二维数字来存放记录了

因为一维数组会导致数据被覆盖,并且出现紊乱,而二维可以完好的保存最佳方案的记录。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值