题意简述
题目链接
题目描述
有一个 r × c r\times c r×c 的矩阵 a a a,矩阵上的每个点都有一个对应的数值,经过这个点就可以取得对应的数值。
现在位于位置 ( 1 , 1 ) (1,1) (1,1),只能往右或者往下走,终点为 ( r , c ) (r,c) (r,c)。
最大化取得的数值。
题目有多组测试数据。
样例输入
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
样例输出
8
16
思路分析
这是一道经典的数字三角形模型的题目,仅将 数字三角形 三角形改为矩形。
状态表示
对于矩阵中的点 ( i , j ) (i,j) (i,j),定义状态 f i , j f_{i,j} fi,j。
为什么是这样定义呢?很遗憾,这个问题没有通解(这个是 NP 完全问题),但是只要做多了题,就能够想出来。最后在总结数字三角形模型的时候,笔者会对这种方法做一种解释。
集合
由于路径是 ( 1 , 1 ) → ( i , j ) (1,1)\to(i,j) (1,1)→(i,j),而且题目中要求的是取得的数值,所以集合的定义就呼之欲出了。
集合:所有从 ( 1 , 1 ) (1,1) (1,1) 到 ( i , j ) (i,j) (i,j) 的所有路径。
属性
题目要求最大化取得的数值,所以属性当然就是 max \max max 了。
说得具体一点,是路径上所有取得的数值之和的 max \max max。
状态计算
最后一个不同的点是什么?就是从哪个点转移过来。
从上图可以看出
(
i
,
j
)
(i,j)
(i,j) 只能由
(
i
−
1
,
j
)
(i-1,j)
(i−1,j) 和
(
i
,
j
−
1
)
(i,j-1)
(i,j−1) 转移而来,所以可以将集合分为两部分:
左边的集合如何计算?我们可以将其分为两步:
(
1
,
1
)
→
(
i
−
1
,
j
)
→
(
i
,
j
)
(1,1)\to(i-1,j)\to(i,j)
(1,1)→(i−1,j)→(i,j)。
其中, ( 1 , 1 ) → ( i − 1 , j ) (1,1)\to(i-1,j) (1,1)→(i−1,j) 有许多路径,但是它们的描述是:所有从 ( 1 , 1 ) (1,1) (1,1) 到 ( i − 1 , j ) (i-1,j) (i−1,j) 的所有路径。回顾上面的状态表示,可以发现,这个描述不正恰好是 f ( i − 1 , j ) f(i-1,j) f(i−1,j) 的定义吗?所以 ( 1 , 1 ) → ( i − 1 , j ) (1,1)\to(i-1,j) (1,1)→(i−1,j) 就可以用 f i − 1 , j f_{i-1,j} fi−1,j 来表示了。
接下来是 ( i − 1 , j ) → ( i , j ) (i-1,j)\to(i,j) (i−1,j)→(i,j)。这一步中,取得的数值增大了 a i , j a_{i,j} ai,j,所以最终的结果中就要加上这一数值。
综上所述,左边的集合可以这样计算:
f
i
−
1
,
j
+
a
i
,
j
f_{i-1,j}+a_{i,j}
fi−1,j+ai,j。
同理,右边的集合的计算方法:
f
i
,
j
−
1
+
a
i
,
j
f_{i,j-1}+a_{i,j}
fi,j−1+ai,j。
由于属性是 max \max max,所以状态转移方程显而易见: f i , j = max { f i − 1 , j + a i , j , f i , j − 1 + a i , j } = max { f i − 1 , j , f i , j − 1 } + a i , j f_{i,j}=\max\{f_{i-1,j}+a_{i,j},f_{i,j-1}+a_{i,j}\}=\max\{f_{i-1,j},f_{i,j-1}\}+a_{i,j} fi,j=max{fi−1,j+ai,j,fi,j−1+ai,j}=max{fi−1,j,fi,j−1}+ai,j
当然,这道题也可以直接感性理解来做。由于 ( i , j ) (i,j) (i,j) 由 ( i − 1 , j ) (i-1,j) (i−1,j) 和 ( i , j − 1 ) (i,j-1) (i,j−1) 转移而来,而要最大化取得的数值,所以 ( i , j ) (i,j) (i,j) 应该从两个点中之前取得的数值(即 f i − 1 , j f_{i-1,j} fi−1,j 和 f i , j − 1 f_{i,j-1} fi,j−1)最大的点转移过来,保证这一步是最优的。
代码实现
Q1:
a
a
a 数组呢?
A1:因为
a
a
a 数组在读入后就再也没有用过,那么就可以直接用
f
f
f 数组来读入并且进行计算。相当于用
f
f
f 覆盖了
a
a
a。
Q2:为什么可以一边读入一边计算?
A2:因为
f
i
,
j
f_{i,j}
fi,j 仅与
f
i
−
1
,
j
f_{i-1,j}
fi−1,j 和
f
i
,
j
−
1
f_{i,j-1}
fi,j−1 相关,而循环顺序是
i
,
j
i,j
i,j 均从小到大枚举,所以计算
f
i
,
j
f_{i,j}
fi,j 时
f
i
−
1
,
j
,
f
i
,
j
−
1
f_{i-1,j},f_{i,j-1}
fi−1,j,fi,j−1 都已经更新过了,可以直接拿来使用。
Q3:为什么不用考虑边界条件?
A3:因为题目中保证了
a
a
a 中的数字均为正整数,而全局变量中边界的值都是
0
0
0,非边界的值更新过后必然大于
0
0
0,所以边界的值时不会被 max()
选中。当然,
(
1
,
1
)
(1,1)
(1,1) 的时候两个前驱值都是
0
0
0,但是由于
f
1
,
1
=
a
1
,
1
f_{1,1}=a_{1,1}
f1,1=a1,1,所以这样写的话两个代码时等价的。
#include <iostream>
const int MAX = 110;
int T, R, C;
int f[MAX][MAX];
int main() {
for (std::cin >> T; T; --T) {
std::cin >> R >> C;
for (int i = 1; i <= R; ++i)
for (int j = 1; j <= C; ++j)
std::cin >> f[i][j], f[i][j] += std::max(f[i - 1][j], f[i][j - 1]);
// 直接使用状态转移方程进行计算
std::cout << f[R][C] << std::endl;
// 根据定义,f[R][C] 就是所有从 (1,1) 到 (R,C) 的所有路径取得的数值的最大值,也就是答案了。
}
return 0;
}