- > 动规讲解基础讲解四——矩阵取数
给定一个m行n列的矩阵,矩阵每个元素是一个正整数,你现在在左上角(第一行第一列),你需要走到右下角(第m行,第n列),每次只能朝右或者下走到相邻的位置,不能走出矩阵。走过的数的总和作为你的得分,求最大的得分。
初看此题,你的思路是什么?
(1) 贪心? 先走到大的数再说?看这个例子:
![](http://img.51nod.com/upload/000FBE97/08D24BF83D0592320000000000000009.png)
无论你以什么方式走到3,总和都是1 + 1 + 3 + 1 + 1 + 1 + 1 = 9
我们为了1个3,放弃了那么多个2, 不值啊。如果我们放弃3而走那些2, 得到的和是1 + 1 + 2 + 2 + 2 + 1 + 1 = 10
看来贪心是错的! 因为我们走到最大值时有很多值不能走到了,一个最大值会把我们带沟里去。
我们为了1个3,放弃了那么多个2, 不值啊。如果我们放弃3而走那些2, 得到的和是1 + 1 + 2 + 2 + 2 + 1 + 1 = 10
看来贪心是错的! 因为我们走到最大值时有很多值不能走到了,一个最大值会把我们带沟里去。
(2) 枚举?枚举是万能的嘛。
可以试试,有多少条可能的路径?
可以试试,有多少条可能的路径?
你需要往下走(m – 1)次,往右走(n – 1)次,才能走到右下角,一共走m + n – 2步!可行路径的总条数有C(m + n – 2, m - 1)。
每条路径长度是m + n – 1 ,所以总的时间复杂度是O(C(m + n – 2, m – 1) * (m + n – 1)), 试试看m = n = 100时,这个数有多大吧!
每条路径长度是m + n – 1 ,所以总的时间复杂度是O(C(m + n – 2, m – 1) * (m + n – 1)), 试试看m = n = 100时,这个数有多大吧!
![](http://img.51nod.com/upload/000FBE98/08D24CCD942471F60000000000000002.png)
没办法了么?
(19899)=22750883079422934966181954039568885395604168260154104734000
想想看,问题有什么性质?
![](http://img.51nod.com/upload/000FBE98/08D24CD34C0AE9440000000000000003.png)
我们只能往右边或者下边走,意味着“不走回头路”,就是说矩阵里面每个位置最多只会经过一次。其实很多地方是“没有机会”经过的。比如我现在在第x行第y列,不管之前走的路径是什么样子,则它左边和上边的位置都是不可能再走到的,对吗?也就是说,我先在在矩阵第x行第y列,并假设以它为原点把矩阵分成四个“象限”,只有第四象限的位置才有可能从这以后经过 (当然还包括横轴的正半轴)!
假设我们从起点走到终点的过程中经过第x行第y列某个位置,为了从起点到终点得到的和最大,那么从起点到第x行第 y列这个位置经过的数的和也一定要最大。这几乎是显然的,但是你要刨根问底的话,可能要问为什么会这样呢?
我们定义集合A是从起点到第x行第y列的全部路径集合,定义集合B是从第x行第y列到终点的全部路径集合。那么起点到终点的路径实际上是子路径a∈A和子路径b∈B的连接(注意删掉第x行第y列这个点,否则走了两次了,呵呵)。
即所有经过第x行第y列的路径都可以划分到A和B这两个集合里,而且任何a∈A和子路径b∈B都可以拼接出一条经过第x行第y列的路径。
那么我要选择一条经过x,y的能得到最大值的路径,显然要选择A集合里路径和最大的a,(其实还要选B集合里和路径和最大的b)。
说了这么多,其实就是想明确一个事:从起点到终点的最优路径上经过了(m + n – 1)个点,则这条路径上对应起点到这(m + n – 1)个点的子路径也都从起点到该位置的所有路径中和最大的路径。
即所有经过第x行第y列的路径都可以划分到A和B这两个集合里,而且任何a∈A和子路径b∈B都可以拼接出一条经过第x行第y列的路径。
那么我要选择一条经过x,y的能得到最大值的路径,显然要选择A集合里路径和最大的a,(其实还要选B集合里和路径和最大的b)。
说了这么多,其实就是想明确一个事:从起点到终点的最优路径上经过了(m + n – 1)个点,则这条路径上对应起点到这(m + n – 1)个点的子路径也都从起点到该位置的所有路径中和最大的路径。
那么假设我们定义f(int x,int y)表示从起点到第x行第y列的最优路径上的数之和,并假设这个矩阵事个二维数组A[][] (下标从1开始)
我们考虑一下,我们如何才能到(x,y)?前一步要么到(x – 1, y), 要么到(x, y – 1),因为有且只有这两个位置能到(x,y),那么怎样才能得到f(x,y)?按我们前面说的,如果从起点达到(x,y)的最优路径要经过(x – 1,y)或者(x,y – 1)则,从起点到达(x – 1,y)或者(x,y – 1)的路径一定也必须是最优的。
那么按照我们对f的定义,我们有从起点达到x,y的最优路径有两种可能:
要么f(x – 1, y) + A[x][y]
要么f(x, y – 1) + A[x][y]
我们要取最优,那自然取较大的
因此有f(x, y) = max(f(x – 1, y) , f(x, y – 1) ) + A[x][y]
这样原来要枚举指数条路径,现在对于每个位置只有两种情况啦。
我们考虑一下,我们如何才能到(x,y)?前一步要么到(x – 1, y), 要么到(x, y – 1),因为有且只有这两个位置能到(x,y),那么怎样才能得到f(x,y)?按我们前面说的,如果从起点达到(x,y)的最优路径要经过(x – 1,y)或者(x,y – 1)则,从起点到达(x – 1,y)或者(x,y – 1)的路径一定也必须是最优的。
那么按照我们对f的定义,我们有从起点达到x,y的最优路径有两种可能:
要么f(x – 1, y) + A[x][y]
要么f(x, y – 1) + A[x][y]
我们要取最优,那自然取较大的
因此有f(x, y) = max(f(x – 1, y) , f(x, y – 1) ) + A[x][y]
这样原来要枚举指数条路径,现在对于每个位置只有两种情况啦。
有了递推关系还不够,有初值才能求解。
那我们看一下 f(1,1),显然这是在起点,没的选f(1,1) = A[1][1]。
那么按照递推式 f(1,2) = max(f(0, y) , f(1,1)) + A[1][2], 但是我们对f(0, y)没有定义呀!考虑下实际意义,这表示要么我们从上面到达(1,2)要么从左面到达(1,2),可是上面没有位置过来啊,这种说明没的选。所以我们可以定义f(0, y) = -∞, 同理我们也可以定义f(x, 0) = -∞。
那我们看一下 f(1,1),显然这是在起点,没的选f(1,1) = A[1][1]。
那么按照递推式 f(1,2) = max(f(0, y) , f(1,1)) + A[1][2], 但是我们对f(0, y)没有定义呀!考虑下实际意义,这表示要么我们从上面到达(1,2)要么从左面到达(1,2),可是上面没有位置过来啊,这种说明没的选。所以我们可以定义f(0, y) = -∞, 同理我们也可以定义f(x, 0) = -∞。
那么总结一下我们的递推式
![](http://img.51nod.com/upload/000FBE98/08D24CD8669316700000000000000005.png)
分析一下这个算法的时间复杂度? 显然是O(m * n),空间复杂度也一样,因为我们打出了一张(m + 1) * (n + 1)的表格——因此空间也是O(m * n)的。
![](http://img.51nod.com/upload/000FBE98/08D24CD8669316700000000000000005.png)
分析一下这个算法的时间复杂度? 显然是O(m * n),空间复杂度也一样,因为我们打出了一张(m + 1) * (n + 1)的表格——因此空间也是O(m * n)的。
![](http://img.51nod.com/upload/000FBE98/08D24CDA8D506CCC0000000000000006.png)
再走一步?
我们找到了最大的和,如何得到和最大的路径呢? 还是从递推式入手,我们发现如果f(x,y) = f(x – 1,y) + A[x][y] 则它是从上面过来的,所以前一个位置是(x – 1, y)
否则 f(x, y) = f(x, y – 1) + A[x][y]则它是从左面过来的,所以前一个位置是(x, y- 1)。
这看起来最优路径被唯一确定了? 不是的,事实上当f(x – 1,y) = f(x, y – 1)时,前一个位置在上面或者左面都可以——所以路径还是很多很多的!
通过这个例子,你是不是对动态规划有了更深刻的理解呢?
我们找到了最大的和,如何得到和最大的路径呢? 还是从递推式入手,我们发现如果f(x,y) = f(x – 1,y) + A[x][y] 则它是从上面过来的,所以前一个位置是(x – 1, y)
否则 f(x, y) = f(x, y – 1) + A[x][y]则它是从左面过来的,所以前一个位置是(x, y- 1)。
这看起来最优路径被唯一确定了? 不是的,事实上当f(x – 1,y) = f(x, y – 1)时,前一个位置在上面或者左面都可以——所以路径还是很多很多的!
通过这个例子,你是不是对动态规划有了更深刻的理解呢?
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int n,a[600][600],dp[600][600]; int main() { cin>>n; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ cin>>a[i][j]; dp[i][j]=-999999; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dp[i][j]=max(dp[i][j-1],dp[i-1][j])+a[i][j]; cout<<dp[n][n]; }
如果对你有所帮助,别忘了加好评哦;么么哒!!下次见!88
细雨斜风作晓寒。淡烟疏柳媚晴滩。入淮清洛渐漫漫。
雪沫乳花浮午盏,蓼茸蒿笋试春盘。人间有味是清欢。