背包九讲三、 多重背包问题

前面讲了什么是完全背包问题,完全背包问题就是每个物品你可以使用无数次,而今天所要说的多重背包问题,每个物品都对应有限的数量。这个问题与完全背包问题很相似,只需要在完全背包问题的状态转移方程上稍微做以改动即可。

F[i, v] = max(F[i-1, v-k*C[i]] + W[i] | 0<=k<=M[i])

时间复杂度为O( V\sum M[i] )

import java.util.Arrays;
import java.util.Scanner;

public class Bag_muti {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入物品的个数: ");
		int N = Integer.parseInt(sc.nextLine());
		System.out.println("请输入背包的大小: ");
		int V = Integer.parseInt(sc.nextLine());
		System.out.println("请输入各个物品的价值: ");
		int[] W = new int[N];
		String[] str1 = sc.nextLine().split(" ");
		for (int i=0; i < N; i++) {
			W[i] = Integer.parseInt(str1[i]);
		}
		int[] C = new int[N];
		System.out.println("请输入各个物品的花费: ");
		String[] str2 = sc.nextLine().split(" ");
		for (int i=0; i < N; i++) {
			C[i] = Integer.parseInt(str2[i]);
		}
		int[] M = new int[N];
		System.out.println("请输入各个物品的个数: ");
		String[] str3 = sc.nextLine().split(" ");
		for (int i=0; i < N; i++) {
			M[i] = Integer.parseInt(str3[i]);
		}
		System.out.println("您输入的物品价值数组为: ");
		System.out.println(Arrays.toString(W));
		System.out.println("您输入的物品的花费数组为: ");
		System.out.println(Arrays.toString(C));
		System.out.println("您输入的物品的个数数组为: ");
		System.out.println(Arrays.toString(M));
		int res = maxValue1(N, V, W, C, M);
		System.out.println("最大价值为:");
		System.out.println(res);
	}
	private static int maxValue1(int N, int V, int[] W, int[] C, int[] M) {
		int [][] F = new int[N+1][V+1];
		for (int i = 0; i < N+1; i++) {
			F[i][0] = 0;
		}
		for (int j = 0; j < V+1; j++) {
			F[0][j] = 0;
		}
		for (int i=1; i < N+1; i++) {
			for (int j=1; j < V+1; j++) {
				int res = F[i-1][j-1]; 
				for (int k=0; k*C[i-1]<=j && k<=M[i-1]; k++) {
					int tmp = F[i- 1][j-k*C[i-1]] + k*W[i-1];
					if (tmp > res) {
						res = tmp;
					}
				}
				F[i][j] = res;
			}
		}
		return F[N][V];
		
	}

}

当然我们也可以像完全背包问题那样,将多重背包问题转化为0、1背包问题。而如果仅仅是将第i件物品复制M[i]份,那么时间复杂度是不会减小的,因此想到也可以使用二进制优化的方法解决多重背包问题。将i种物品分成若干件0、1背包中的物品,其中每件物品都有一个系数,这件物品的费用及价值都等于原来的费用和价值乘以这个系数。系数的求法就是之前提到的二进制优化方法,例如13

2^{0}2^{1}2^{2}2^{3}

由于这四项的和刚好大于13,而我们不能让这几个数组合出大于13的整数,因此我们最后一位应该是13-1-2-4=6

因此我们可以知道0~13之间所有的数都可以使用0、1、2、4、6组合出来,因此这样转化为0、1规划问题,时间复杂度为

O(V\sum logM[i])

背包九讲三、 多重背包问题_第1张图片

 这个是背包九讲中给出的伪代码,第一次看时我是没太看懂的,下面我们可以写出代码来看这个程序到底是咋回事。

首先就是实现CompletePack() ZeroOnePack()两个函数

public class Bag_muti {
	static Scanner sc = new Scanner(System.in);
	public static int N = Integer.parseInt(sc.nextLine());
	static int[] dp = new int[N];

        // 省略主函数,前面有
	private static void ZeroPack(int c, int w, int V) {
		// c为当前物品的花费,w为当前物品的价值,V为背包总体积,本函数相当于用当前遍历到的物品来更新dp函数
		for (int i=V; i>=c; i--) {
			dp[i] = Math.max(dp[i-1], dp[i-1]+w);
		}
		
	}
	
	private static void CompletePack(int c, int w, int V) {
		// c w V的定义与上相同,是完全背包的更新方法,与0、1背包只有细微差别
		for (int i=c; i<=V; i++) {
			dp[i] = Math.max(dp[i-1], dp[i-1]+w);
		}
	}

现在分析上述伪代码,我们还是像前几个背包问题一样,对每个物品进行遍历,如果将所有当前遍历到的物品都放入背包,其花费的空间要大于背包的容量,那就相当于这个物品可以无限的取,因为是永远也取不完的,这时候对于当前物品可以用完全背包的方法来套用。反之,如果可以取完所有的物品,那么我们就需要选择取多少个当前物品,也就是要使用二进制优化方法。

	private static int maxValue2(int N, int V, int[] W, int[] C, int[] M) {
		// dp里的元素先全部赋予0
		for (int i=0; i < N; i++) {
			dp[i] = 0;
		}
		for (int i=1; i < N; i++) {
			// 如果将所有当前物品都放入背包花费超过背包总容积的话 就用完全背包套
			if (M[i]*C[i] > V) {
				CompletePack(C[i], W[i], V);
			}
			else {
				int k = 1;
				while(k < M[i]) {
					ZeroPack(k*C[i], k*W[i], V);
					M[i] -= k;
					k *= 2;
				}
				if (M[i] > 0) {
					ZeroPack(M[i]*C[i], M[i]*W[i], V);
				}
				
				
			}
		}
		return dp[V];
	}

 

你可能感兴趣的:(背包九讲)