> 文档中心 > 细说背包问题 - 导入

细说背包问题 - 导入


从“数字金字塔”说起

观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。
在这里插入图片描述
在上面的样例中,13 -> 8 -> 26 -> 15 -> 24的路径得到的和最大:86。

解题

  • 我们要在所有路径中,找到经过数字之和的最大值。每条路径都是可逆的,同一条路径,从上往下和从下往上,得到的数字之和是相同的。
  • 如果自上而下枚举的话,到最下面的第 n 行将得到 n 个数字,还需要再打擂台找最大值,多出一个步骤。我们采用自下而上枚举,到第一行就得到唯一的结果。
  • 约定用[i][j]表示从第 n 行的某一列出发,到达第 i 行第 j 列的所有路径中数字之和的最大值, 要到达第 i 行第 j 列的位置,只有经过第 i + 1 行第 j 列,或者第 i + 1行第 j + 1列,也就是说
    f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j];
  • 递推到第 1 行第 1 列时,f[1][1]就是题目所求的最优路径的数字之和

这是完整代码

#include using namespace std;const int N = 1009;int n, a[N][N];int f[N][N];int main() {cin >> n;for (int i = 1; i <= n; i ++) {for (int j = 1; j <= i; j ++) {cin >> a[i][j];}}for (int i = n; i >= 1; i --) {for (int j = 1; j <= i; j ++) {f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j];}}cout << f[1][1];return 0;}

优化

  • 回忆一下变量赋值语句a = a + b,是什么意思?它表示先获取变量 a 和变量 b 的值,将它们相加,再赋值给变量 a。注意前后顺序,先获取变量ab的值,运算后,再赋值给变量a,实现更新变量a的目的。
  • 再来观察上面代码的核心语句
    f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j];
    将其改成下面这条语句,如何理解?
    f[j] = max(f[j], f[j + 1]) + a[i][j];
    它表示,先获取f[j]f[j + 1]的值,运算后,再赋值给f[j],实现更新数组元素f[j]的目的
  • 我们在自下而上推算最大数字和的过程中,只关心前一步的数值对目前这一步的影响,而更早步骤得到的数值并没有保存的价值。也就是说,我们不必使用二维数组(实际上,二维数组的每一行可以看做是保存了自下而上每一步的中间结果),用一维数组就够了
  • 重新思考这道题,我们先用f[j]表示自下而上走到第 i + 1 行第 j 列的所有路径中数字之和的最大值,用f[j + 1]表示自下而上走到第 i + 1 行第 j + 1 列的所有路径中数字之和的最大值,哪个位置的数值大,就选择从哪个位置向上走到第 i 行第 j 列,加上a[i][j]后,就得到自下而上走到第 i 行第 j 列的所有路径中数字之和的最大值,再将这个最优结果保存在f[j]中,用赋值语句来表示:
    f[j] = max(f[j], f[j + 1]) + a[i][j]
  • 递推到第 1 行第 1 列时,f[1]就是题目所求的最优路径的数字之和

优化后的代码,仅使用一维数组,所需空间大大减少

#include using namespace std;const int N = 1009;int n, a[N][N];int f[N];int main() {cin >> n;for (int i = 1; i <= n; i ++) {for (int j = 1; j <= i; j ++) {cin >> a[i][j];}}for (int i = n; i >= 1; i --) {for (int j = 1; j <= i; j ++) {f[j] = max(f[j], f[j + 1]) + a[i][j];}}cout << f[1];return 0;}

延伸

  • 在内循环中,变量 j 从 1 到 i,对应着:
    f[1] = max(f[1], f[2]) + a[i][1];,用f[1]f[2]更新f[1],
    f[2] = max(f[2], f[3]) + a[i][2];,用f[2]f[3]更新f[2],
    ......
    每一次更新的 f 数组元素,都不会影响接下来的赋值语句
  • 假如内循环中,变量 j 从 i 到 1,对应着:
    ......
    f[2] = max(f[2], f[3]) + a[i][2];
    f[1] = max(f[1], f[2]) + a[i][1];,新的f[2]被用来更新f[1],
    每一次更新的 f 数组元素,都会影响接下来的赋值语句。显然,这样得到的结果就不对了。
  • 结论对我们后面的学习非常有用。在将二维数组压缩到一维数组的时候,循环变量 j 是一维数组 f 的下标,如果赋值语句右边的下标
    – 只出现j + *,循环变量j应该从小到大递增
    – 只出现j - *,循环变量j应该从大到小递增
    – 有的是j + *,有的是j - *,则无法将二维数组压缩到一维数组,因为在循环的过程中,前一次更新的数组元素,都会对后面的赋值语句产生影响