Un análisis en profundidad del problema de las mochilas múltiples (Parte 2)

Este artículo ha participado en el evento "Ceremonia de creación de recién llegados" para comenzar juntos el camino de la creación de oro.

Un análisis en profundidad del problema de las mochilas múltiples (Parte 2)

prefacio

En los tres artículos anteriores, hemos discutido cuidadosamente el problema de la mochila 01, el problema de la mochila completa y la primera parte de las mochilas múltiples. En este artículo, presentamos principalmente un método de optimización para el problema de las mochilas múltiples : optimización binaria de mochilas múltiples , si no has leído la primera parte de la mochila múltiple , primero debes leer la mochila múltiple .

Introducción al problema de la mochila múltiple

Tener norte norte elementos y una capacidad es EN EN La mochila deel primero i i artículoscomo máximo s i si piezas, el volumen de cada pieza es en i v_i , el valor es en i Wisconsin . Encuentre qué artículos poner en la mochila para que el volumen total de los artículos no exceda la capacidad de la mochila y el valor total sea el mayor.

Nota : Los caracteres utilizados anteriormente tienen el mismo significado a lo largo de este artículo.

La diferencia entre el problema de la mochila múltiple y la mochila 01 y la mochila completa es la cantidad de veces que se puede usar el artículo . La mochila 01 solo se puede usar una vez, la mochila múltiple se puede usar innumerables veces y la mochila múltiple se puede usar utilizado varias veces.

Optimización binaria de múltiples mochilas

Implementación de optimización binaria

Antes de analizar formalmente la optimización binaria de múltiples mochilas , analicemos la complejidad temporal de múltiples mochilas , asumiendo que tenemos norte norte artículos, el número promedio de cada artículo es S S , la complejidad temporal de múltiples mochilas es O ( norte S EN ) O (URSS) .La optimización binaria de múltiples mochilas puede reducir esta complejidad de tiempo a O ( N V l o g ( S ) ) O(NVlog(S))

En la primera parte de la multimochila mencionamos la ecuación de transferencia dinámica de la multimochila ( T = m i n ( S , V v i ) T = min(S, \frac{V}{v_i}) ,其中 S S 表示物品能够选择的次数, v i v_i 表示物品的体积, V V 表示当前背包的容量):

d p [ i ] [ j ] = m a x { d p [ i 1 ] [ j ] , d p [ i 1 ] [ j v [ i ] ] + w [ i ] , d p [ i 1 ] [ j v [ i ] 2 ] + w [ i ] 2 , . . . , d p [ i 1 ] [ j v [ i ] T ] + w [ i ] T } dp[i][j] = max\\ \{ \\ dp[i - 1][j], \\ dp[i - 1][j - v[i]] + w[i],\\ dp[i - 1][j - v[i] * 2] + w[i] * 2, \\ ..., \\ dp[i - 1][j - v[i] * T] + w[i] * T\\ \}

从上面的公式我们可以知道,对于某个有 S S 件的物品当中,我们要选择一个合适的数字使得我们的收益最大,这个数字可能是 1 , 2 , 3 , 4 , . . . , S 1, 2, 3, 4, ..., S 。我们在文章多重背包上篇提到我们可以将多重背包转化成01背包,我们将 S S 个物品在逻辑上分成体积和价值相同的 S S 个不同的物品,被分成 S S 个不同的物品在进行动态选择的时候与 S S 个相同的物品是一样的。比如说对于 S S 个相同的物品 A A ,我们在选择3个的时候收益可以达到最大,那么对于转化之后的01背包问题来说就选择3个与 A A 体积和价值相同的物品即可。

根据上面分析我们可以知道多重背包能够转化成01背包的原因就是多重背包在转化为01背包之后,01背包能够有多重背包选1个,选2个,选3个,...,选 S S 个的效果。

而我们的二进制优化也主要集中在这个地方。多重背包的二进制优化也是将多重背包问题转化成01背包问题,但是不是将 S S 个相同的物品转化成 S S 个体积和价值相同的不同的物品。根据上文的分析我们知道,我们在将多重背包转化成01背包之后是需要保证01背包能够实现多重背包选1个,选2个,选3个,...,选 S S 个的效果。那么我们如何实现这一点呢?下面代码主要显示二进制优化将多重背包转化成01背包该往装物品的价值和体积的集合里加入什么东西。

Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int V = scanner.nextInt();
ArrayList<Integer> v = new ArrayList<>();
ArrayList<Integer> w = new ArrayList<>();
for (int i = 0; i < N; i++) {
    // 这个表示第 i 个物品的体积
    int vi = scanner.nextInt();
    // 这个表示第 i 个物品的价值
    int wi = scanner.nextInt();
    // 这个表示第 i 个物品有多少个
    int si = scanner.nextInt();
    // 这段代码主要是实现多重背包能够选择1个
    // 选择2个,...,选择S个的效果
    for (int j = 1; j <= si; j *= 2) {
        si -= j ;
        v.add(vi * j);
        w.add(wi * j);
    }
    if (si > 0) {
        v.add(vi * si);
        w.add(wi * si);
    }
}

我们举一个例子来分析上面的代码,假设我们加入一个物品 A A ,它的个数为9,价值和体积分别为5和3。那么在集合 v v 和集合 w w 当中的数据分别为:

v = [ 3 , 6 , 12 , 6 ] v = [3, 6, 12, 6]
w = [ 5 , 10 , 20 , 10 ] w = [5, 10, 20, 10]

上面的例子将9个 A A 分成了 A 1 A_1 A 2 A_2 A 3 A_3 ,以及 A 4 A_4 A 1 A_1 A 4 A_4 的体积和价值分别相当于1个,2个,4个,2个的 A A 的体积和价值。我们在上文当中提到了,我们在将多重背包转化成01背包之后是需要保证01背包能够实现多重背包选1个,选2个,选3个,...,选 S S 个的效果,那么上面的转化是如何实现这个效果的呢?

  • 一个 A A :相当于 A 1 A_1
  • 两个 A A :相当于 A 2 A_2
  • 三个 A A :相当于 A 1 + A 2 A_1 + A_2 ,也就是在动态选择的时候选择了 A 1 A_1 A 2 A_2 两个物品。
  • 四个 A A :相当于 A 3 A_3
  • 五个 A A :相当于 A 1 + A 3 A_1 + A_3 ,也就是在动态选择的时候选择了 A 1 A_1 A 3 A_3 两个物品。
  • 六个 A A :相当于 A 2 + A 3 A_2 + A_3 ,也就是在动态选择的时候选择了 A 2 A_2 A 3 A_3 两个物品。
  • 七个 A A :相当于 A 1 + A 2 + A 3 A_1 + A_2 + A_3 ,也就是在动态选择的时候选择了 A 1 A_1 A 2 A_2 A 3 A_3 三个物品。
  • 八个 A A :相当于 A 2 + A 3 + A 4 A_2 + A_3 + A_4 ,也就是在动态选择的时候选择了 A 2 A_2 A 3 A_3 A 4 A_4 三个物品。
  • 九个 A A :相当于 A 1 + A 2 + A 3 + A 4 A_1 + A_2 + A_3 + A_4 ,也就是在动态选择的时候选择了 A 1 A_1 A 2 A_2 A 3 A_3 A 4 A_4 四个物品。

相信经过上面的例子之后你已经大致明白了二进制优化的大致实现过程,二进制优化也是将多重背包转化成01背包但是和之前的转化不同的是,我们不是将 S S 个物品 A A 划分成 S S 个体积和价值相同的物品,而是将其划分成体积和价值是原来的物品1倍、2倍、3倍,...., 2 n 2^n 倍的物品,即 1 + 2 + 4 + . . . + 2 n + a = S 1 + 2 + 4 + ... + 2^n + a = S ,其中 a a 是最后的剩下的余数( a < 2 n + 1 a \lt 2^{n + 1} ),比如上面最后一个2就是a = 9 - 1 - 2 - 4。这样的划分我们可以知道,我们划分之后的物品的数目会少非常多。如果物品的次数的最大值是int类型的最大值,如果我们一个一个的划分最多可以划分超过20亿个物品,而上面的划分方式,我们划分出来的物品不会超过32个,因此大大降低了时间复杂度。

之前一个一个的划分我们的时间复杂度为 O ( N S V ) O(NSV) ,而像上面那样划分我们最大的时间复杂度为 O ( N V l o g ( S ) ) O(NVlog(S)) ,其中 N N 表示物品的个数, S S 表示物品能够选择的平均次数, V V 表示背包的容量。

上面就是我们使用二进制优化方式将多重背包转化成01背包的方式,完整代码如下(下方代码使用了单行数组优化):

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

public class Main {
  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int N = scanner.nextInt();
    int V = scanner.nextInt();
    ArrayList<Integer> v = new ArrayList<>();
    ArrayList<Integer> w = new ArrayList<>();
    for (int i = 0; i < N; i++) {
      int vi = scanner.nextInt();
      int wi = scanner.nextInt();
      int si = scanner.nextInt();
      for (int j = 1; j <= si; j *= 2) {
        si -= j ;
        v.add(vi * j);
        w.add(wi * j);
      }
      if (si > 0) {
        v.add(vi * si);
        w.add(wi * si);
      }
      System.out.println(v);
      System.out.println(w);
    }
    int[] f = new int[V + 1];
    for (int i = 0; i < v.size(); i++) {
      for (int j = V; j >= v.get(i); j--) {
        f[j] = Math.max(f[j], f[j - v.get(i)] + w.get(i));
      }
    }
    System.out.println(f[V]);
  }
}

二进制优化的本质

我们知道任何一个数都有他的二进制形式,任何一个数都可以由2的整数次幂相加得到:

假如我们有15个物品 A A ,那么我们会得到 A 1 A_1 A 2 A_2 A 3 A_3 A 4 A_4 ,他们的价值和体积分别是 A A 的1倍,2倍,4倍和8倍,这四个物品可以组成相当于任意整数倍的物品 A A 的价值和重量,在这个问题当中就是1, 2, 4, 8可以组成1~15之间任意一个数。

因为我们最终可能 S S 个物品当中全部选了,因此当我们将多重背包转化成01背包之后,所有转化之后的物品的价值和体积需要和 S S 个物品相同,而 S S 不一定恰好就是 n n 个整数幂的值相加,因此在上文当中还提到了 a a a a 就保证了我们最终可以取到1~ S S 之间任意一个数。

总结

本篇文章主要给大家介绍的多重背包问题的二进制优化,里面的逻辑还是稍微有点复杂的,可能需要大家仔细去体会,大家在看文字的时候可以参考代码仔细分析,可以理解的更好一点。

以上就是本篇文章的所有内容了,希望大家有所收获,我是LeHung,我们下期再见!!!(记得点赞收藏哦!)


更多精彩内容合集可访问项目:github.com/Chang-LeHun…

关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。

Supongo que te gusta

Origin juejin.im/post/7120926384822124575
Recomendado
Clasificación