细说背包问题 - 导入

从“数字金字塔”说起

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

解题
  • 我们要在所有路径中,找到经过数字之和的最大值。每条路径都是可逆的,同一条路径,从上往下和从下往上,得到的数字之和是相同的。
  • 如果自上而下枚举的话,到第 n 行将得到 n 个数字,还需要再打擂台找最大值,多出一个步骤。我们采用自下而上枚举,到第一行就得到唯一的结果。
  • 约定用f[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 <bits/stdc++.h>
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 <bits/stdc++.h>
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 的下标,如果赋值语句右边的下标
    (1) 只出现j + *,循环变量j应该从小到大递增
    (2) 只出现j - *,循环变量j应该从大到小递减
    (3) 有的是j + *,有的是j - *,则无法将二维数组压缩到一维数组,因为在循环的过程中,前一次更新的数组元素,会对后面的赋值语句产生影响
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值