完全背包转化为多重背包

完全背包转化为多重背包

前言

在本篇文章当中主要给大家介绍如何将完全背包问题转化成多重背包问题,在前面的文章完全背包当中,我们仔细的介绍了完全背包的状态转移方程、根据状态转移方程如何完成代码以及多重背包的数组优化的原理,为什么这种优化能够有效!本篇文章主要专注于如何将完全背包转化成多重背包。如果你还不了解多重背包可以先阅读深入剖析多重背包问题(上篇)深入剖析多重背包问题(下篇)

完全背包问题

\(N\)种物品和一个容量是 \(V\)的背包,每种物品都有无限件可用。第\(i\) 种物品的体积是 \(v_i\),价值是\(w_i\)。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

完全背包问题和01背包的唯一区别就在于物品的个数,在01背包当中所有的物品只有一件,也就只能使用一次。而在完全背包当中物品可以使用无限多次。

比如下面的4个物品,背包能够承受的最大重量为5,我们应该如何选择,使得我们获得的总价值最大:

物品 重量 价值
A 1 2
B 2 4
C 3 4
D 4 5

这个问题还是比较简单,我们直接从图中看,我们可以选择五个A或者两个B一个A,可以产生最大的收益,最大收益为10。

动态转移方程

在前面的文章完全背包当中我们仔细介绍了完全背包的动态转移方程。我们用一个二位数组dp[i][j]表示当只使用前i个物品且背包容量为j的时候,我们能够获取的最大的收益。

和01背包问题一样首先对于第i个物品,首先需要判断背包是否能够容纳(v[i]表示第i件物品的体积,w[i]表示第i件物品的价值):

  • 如果背包的容量大于等于第i个物品的体积,那我们就有两种选择:
    • 将第i个物品放入背包当中,但是在这里需要注意的一点是完全背包的物品有无数件,因此当我们选择之后我们的转移方程为dp[i][j - v[i]] + w[i],这里不是i-1而是i,因为第i件物品有无数件。
    • 不将第i个物品放入背包当中,那么我们就能够使用容量为j的背包去选择前i - 1个物品,这种情况下我们的最大收益为dp[i - 1][j]
  • 如果背包容量小于第i件物品的体积,我们就不能够选择第i件物品了,这种情况下我们的最大收益为dp[i - 1][j]

基于上面的分析我们可以知道完全背包问题的动态转移方程为:

\[dp[i][j] = \begin{cases}
max(dp[i][j – v[i]] + w[i], dp[i – 1][j]), j \ge v[i]\\
dp[i – 1][j] , j \lt v[i]
\end{cases}
\]

根据上面的状态转移方程,我们的核心代码如下(其中N表示物品的个数,V表示背包的容量,w[i]表示第i个物品的价值,v[i]表示第i个物品所占的体积):

int backpack() {
  for (int i = 1; i < N; ++i) {
    for (int j = 0; j <= V; ++j) {
        if (j >= v[i]) {
            dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i]);
        }
        else {
            dp[i][j] = dp[i - 1][j];
        }
    }
	}
  return dp[V];
}

但是上面的代码我们可以进行数组优化,优化之后的代码如下(如果你还不是很清楚优化原理的话,你可以阅读完全背包当中的数组优化小节):

int backpack() {
	for (int i = 0; i < N; ++i) {
		for (int j = v[i]; j <= V; ++j) {
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	return dp[V];
}

问题转化

我们知道完全背包问题当中的物品是可以无限次使用的,但是实质上我们不可能拿无限个物品,因为我们的背包容量是有限的,假如我们的背包容量为V物品的体积为v,那么我们能够拿的最大的个数就是:

\[\lfloor \frac{V}{v} \rfloor
\]

因此我们这样就将一个完全背包问题转化成了多重背包问题,每一个物品的最大数量就是:

\[\lfloor\frac{V}{v_i}\rfloor
\]

上面的公式简单的说来就是拿的所有的物品所占的体积不能超过背包的容量。因此我们可以重写完全背包的状态转移方程:

\[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_i] + w[i] * T_i\\
\}
\]

其中 \(T_i = \lfloor\frac{V}{v_i}\rfloor\)

因此完全背包转多重背包的代码如下(进行数组优化之后的代码,如果你还不了解数组的优化,可以先阅读完全背包深入剖析多重背包问题(上篇)深入剖析多重背包问题(下篇)当中数组优化的小节):

#include <iostream>

using namespace std;

#define MAX_LENGTH 2000

int N, V;

int values[MAX_LENGTH];
int volumes[MAX_LENGTH];
int dp[MAX_LENGTH];

void complete_backpack() {
  // 前面两侧循环和正常的 dp 循环一样
  for (int i = 0; i < N; ++i) {
    for (int j = V; j >= volumes[i]; j--) {
      // 这里就是根据背包容量进行限制了 j 肯定要大于等于拿的所有物品的容量
      for(int k = 1; j >= volumes[i] * k; k++) {
        dp[j] = max(dp[j], dp[j - volumes[i] * k] + values[i] * k);
      }
    }
  }
}


int main() {
  cin >> N >> V;
  for (int i = 0; i < N; i++) {
    cin >> volumes[i] >> values[i];
  }
  complete_backpack();
  printf("%d", dp[V]);
  return 0;
}

总结

在本篇文章当中主要介绍了入和将完全背包转化成多重背包,我们可以知道完全背包的时间复杂度为\(O(NV)\),但是如果将完全背包转化成多重背包之后时间复杂度为\(O(NVM)\),其中M表示平均每个物品能拿的最大的个数,因此可以知道其实不必将完全背包转化成多重背包,但是可以扩展我们的思维。

将完全背包转化成多重背包的最核心的就是背包容量的限制,我们可以通过背包容量有限制这一条件,知道我们能够拿的最大的物品数量,从而将完全背包转化成多重背包问题。


以上就是本篇文章的所有内容了,我是LeHung,我们下期再见!!!更多精彩内容合集可访问项目://github.com/Chang-LeHung/CSCore

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