完全背包问题

96 阅读2分钟

完全背包问题

  • 问题背景

    • 一个容量是V的背包和N种物品,每种物品的体积是vi,价值是wi,每种物品能无限使用,求能装走物品的最大价值
  • Dp分析

Snipaste_2023-03-21_18-13-49.png

  • 状态表示

    • 二维状态表示f(i,j)
    • f(i,j)表示的是哪一个集合:所有满足如下条件的选法集合
      • 只从前i个物品中选
      • 选出来的物品的总体积小于等于j
    • f(i,j)存的是什么属性:MaxMin数量;在这里f(i,j)存的应该是最大值,即所有满足这种选法的最大值
  • 状态计算:f(i,j)可以怎么算出来?

    • 完全背包问题将集合划分为k+1个子集
      • 每次选择k个第i个物品,k0 取到 k * vi <= j
      • f(i, j) = f(i - 1, j - vi * k) + wi * k
    • 因此 f(i, j) = max( f(i, j), f(i - 1, j - vi * k) + wi * k )
  • 代码

    • //初始化f[0][0 ~ m]都是0,所以省略
      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][j], f[i - 1][j - V[i] * k] + W[i] * k);
              }
          }
      }
      
  • 优化

    • 观察状态计算的过程

      • f(i, j) = max( f(i - 1, j), f(i - 1, j - vi) + wi, f(i - 1, j - vi * 2) + wi * 2, f(i - 1, j - vi * 3) + wi * 3, …… )
      • f(i, j - vi) = max( f(i - 1, j - vi), f(i - 1, j - vi * 2) + wi, f(i - 1, j - vi * 3) + wi * 2, ……)
      • 由上面两式,可以得出 f(i, j) = max (f(i - 1, j), f(i, j - vi) + wi) ,与k无关
    • 因此可以做如下优化

      • k层循环删掉,变成类似01背包问题的格式
    • 代码

      • //初始化f[0][0 ~ m]都是0,所以省略
        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]);
                }
            }
        }
        
  • 再优化

    • 观察状态计算的过程,可以发现两个特点

      • f(i, j) 首先先会继承 f(i - 1, j) 的值
      • 然后再和当前第i层在它之前就更新完了的 f(i, j - vi) + wi 比较
    • 因此可以做如下优化

      • i 这一维直接优化掉,i 的每一层循环中的 j 都在原先的 j 上进行覆盖
      • j 层循环直接从 vi 开始
    • 代码

      • //初始化f[0][0 ~ m]都是0,所以省略
        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]);
            }
        }
        

练习

  • 题目

Snipaste_2023-03-21_23-39-51.png

  • 题解1
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static int[] V = new int[N];
    public static int[] W = new int[N];
    //只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
    public static int[][] f = new int[N][N];
    public static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        for (int i = 1; i <= n; i++) {
            String[] str2 = br.readLine().split(" ");
            V[i] = Integer.parseInt(str2[0]);
            W[i] = Integer.parseInt(str2[1]);
        }

        //初始化f[0][0 ~ m]都是0,所以省略
        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][j], f[i - 1][j - V[i] * k] + W[i] * k);
                }
            }
        }
        pw.println(f[n][m]);
        br.close();
        pw.close();
    }
}
  • 题解2
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static int[] V = new int[N];
    public static int[] W = new int[N];
    //只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
    public static int[][] f = new int[N][N];
    public static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        for (int i = 1; i <= n; i++) {
            String[] str2 = br.readLine().split(" ");
            V[i] = Integer.parseInt(str2[0]);
            W[i] = Integer.parseInt(str2[1]);
        }

        //初始化f[0][0 ~ m]都是0,所以省略
        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]);
                }
            }
        }
        pw.println(f[n][m]);
        br.close();
        pw.close();
    }
}
  • 题解3
import java.io.*;

public class Main {
    public static final int N = 1010;
    public static int[] V = new int[N];
    public static int[] W = new int[N];
    //只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
    public static int[] f = new int[N];
    public static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        for (int i = 1; i <= n; i++) {
            String[] str2 = br.readLine().split(" ");
            V[i] = Integer.parseInt(str2[0]);
            W[i] = Integer.parseInt(str2[1]);
        }

        //初始化f[0][0 ~ m]都是0,所以省略
        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]);
            }
        }
        pw.println(f[m]);
        br.close();
        pw.close();
    }
}