背包问题
01背包
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N行,每行两个整数 vi,wi,用空格隔开,分别表示第 i件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n,m;
int v[N], w[N];
int f[N][N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> v[i] >> w[i];
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
f[i][j] = f[i - 1][j];
if(j >= v[i]){
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
}
cout << f[n][m] << endl;
}
事实上只需要一维数组保存状态即可,注意01背包中j从大到小遍历,因为当**前层状态f[i]取决于上一层f[i - 1]**的状态,如果从小到大遍历j,那么当前层状态就变成从当前层变过来了
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n,m;
int v[N], w[N];
int f[N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> v[i] >> w[i];
}
for(int i = 1; i <= n; i++){
for(int j = m; j >= v[i]; j--){
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
}
思考:如果问的是背包容量恰好为V时装的最大价值怎么办
完全背包
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
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 sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
int[] v = new int[n + 1], w = new int[n + 1];
for (int i = 1; i <= n; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
int[][] f = 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 * v[i] <= j; k++) {
f[i][j] = Math.max(f[i - 1][j - k * v[i]] + k * w[i], f[i][j]);
}
}
}
System.out.println(f[n][m]);
}
}
优化
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
int[] v = new int[n + 1], w = new int[n + 1];
for (int i = 1; i <= n; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
int[][] f = new int[n + 1][m + 1];
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]);
}
}
}
System.out.println(f[n][m]);
}
}
优化
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
int[] v = new int[n + 1], w = new int[n + 1];
for (int i = 1; i <= n; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
int[] f = new int[m + 1];
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]);
}
}
多重背包
有 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
将多重背包转化为01背包
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
int[] f = new int[m + 1], v = new int[n + 1], w = new int[n + 1], s = new int[n + 1];
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 = m; j >= v[i]; j--) {
for (int k = 1; k <= s[i] && k * v[i] <= j; k++) {
f[j] = Math.max(f[j], f[j - k * v[i]] + k * w[i]);
}
}
}
System.out.println(f[m]);
}
}
上述多重背包的解法时间复杂度为O(n3)
优化思路:每种物品有s个,是否需要枚举1到s到底取了几个?==>不需要,我们可以用二进制表示s,那么枚举物品的个数的复杂度就从O(n)变为了O(log2n)
/**
Good用于重新表示物品
*/
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
static class Good{
int v;
int w;
public Good(int v, int w) {
this.v = v;
this.w = w;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
List<Good> goods = new ArrayList<>();
for (int i = 0; i < n; i++) {
int v = sc.nextInt();
int w = sc.nextInt();
int s = sc.nextInt();
for (int k = 1; k <= s; k++) {
s -= k;
goods.add(new Good(k * v, k * w));
}
if(s > 0){
goods.add(new Good(s * v, s * w));
}
}
int[] f = new int[m + 1];
for(Good good : goods){
for(int j = m; j >= good.v; j--){
f[j] = Math.max(f[j], f[j - good.v] + good.w);
}
}
System.out.println(f[m]);
}
}
混合背包
有 N种物品和一个容量是 V的背包。
物品一共有三类:
- 第一类物品只能用1次(01背包);
- 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用 si次(多重背包);
每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
- si=−1 表示第 i种物品只能用1次;
- si=0 表示第 i 种物品可以用无限次;
- si>0表示第 i种物品可以使用 si次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤≤1000
0<vi,wi≤1000
−1≤si≤1000
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8
混合背包问题思路很简单,枚举每种物品的时候分情况讨论即可
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
static class Good{
int type;
int v;
int w;
public Good(int type, int v, int w) {
this.type = type;
this.v = v;
this.w = w;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
List<Good> goods = new ArrayList<>();
for (int i = 0; i < n; i++) {
int v = sc.nextInt();
int w = sc.nextInt();
int s = sc.nextInt();
if(s < 0){
goods.add(new Good(-1, v, w));
}else if(s == 0){
goods.add(new Good(1, v, w));
}else {
for(int k = 1; k * v <= s; k *= 2){
s -= k * v;
goods.add(new Good(-1, k * v, k * w));
}
if(s > 0){
goods.add(new Good(-1, s * v, s * w));
}
}
}
int[] f = new int[m + 1];
for(Good good : goods){
if(good.type == -1){
for(int j = m; j >= good.v; j--){
f[j] = Math.max(f[j], f[j - good.v] + good.w);
}
}else{
for(int j = good.v; j <= m; j++){
f[j] = Math.max(f[j], f[j - good.v] + good.w);
}
}
}
System.out.println(f[m]);
}
}