1.传送门: http://poj.org/problem?id=1163
2.问题分析:
一。考虑用递归,要求的是从第一行到最后一行的数字加起来的最大值。如果它是最后一行,那么他的最大值就是它本身,如若不是,则是他的下一层 左边最大值 和 右边最大值 中找到最大值,然后和该数本身相加求得这个数的最大值。
做法,
1.用一个二维数组存放数字三角形,如D[ i ][ j ] 代表第i行第j列的数字大小。
2.用函数MaxSum(i , j )代表从D(i,j)到底边的各条路径中的最佳路径之和。
求 MaxSum( 1 , 1 )。
典型递归问题:D(i , j)出发,只能走D(i+1,j)与D(i+1,j+1),故对与N行的三角形,有如下定义。
if(i == n)
MaxSum(i,j) = D(i,j);
else
MaxSum(i,j) = Max{ MaxSum(i+1,j) , MaxSum(i,j+1) ) + D[i][j];
那么很容易,这个程序也就写出来了。 即:
#include <iostream>
#include <algorithm>
#define MAX 101
using namespace std;
int D[MAX][MAX];
int n;
int MaxSum(int i,int j){
if(i==n)
return D[i][j];
int x = MaxSum(i+1,j);
int y = MaxSum(i+1,j+1);
return max(x,y)+D[i][j];
}
int main(){
int i,j;
cin >> n;
for(i=1;i<=n;i++)
for(j=1;j<=i;j++)
cin >> D[i][j];
cout << MaxSum(1,1) << endl;
return 0;
}
一试用例,过了,但是提交就会发现TLE。
为什么呢,地球毁灭你都算不完。(郭炜老师名言) 下面看看这个程序复杂度:
怕不怕 ,我们做汉诺塔时,老师告诉我们,地球毁灭才能得出是多少,那是2的64次,这道题都到2的100次了,那么容易做出来吗?但是,这是由于重复计算引起的,所以,我们可以用记忆递归的办法来解决重复计算。
2.数字三角形记忆递归型动规数组
设立一个数组maxsum(i , j )专门用来记录点(i , j )到底层的最大距离,然后每次调用即可,不用重复计算。
思路是:先设立一个maxsum[N][N],然后赋初始值-1,代表这个点还没有求过最大值(被访问过)。
然后递归访问数组,首先判断是否被访问过,如果是,则它是求出来的最大值,直接用值,否则,递归的把求出来的值赋值给maxsum数组。这样,只需要进行n(n+1)/2次操作,O(n²)的时间复杂度,即可求出来。
下面是代码详解:
/*数字三角形 记忆递归型动规程序*/
#include <iostream>
#include <algorithm>
#define N 101
using namespace std;
int D[N][N];
int maxsum[N][N]; //记录最大值,避免重复计算,时间复杂度为O(n*n)
int n;
/*
底下这个递归程序理解了好久,才差不多明白了怎么回事
1.只有maxsum[i][j] = -1 和 求某一个maxsum[i][j]的值时才return值。
2.其他时候都是在做对maxsum数组的更新。以便直接调用maxsum值。
*/
int MaxSum(int i,int j)
{
if(maxsum[i][j] != -1)
return maxsum[i][j];
if(i==n)
maxsum[i][j] = D[i][j];
else
{
int x = MaxSum(i+1,j);
int y = MaxSum(i+1,j+1);
maxsum[i][j] = max(x,y)+D[i][j];
}
return maxsum[i][j];
}
int main()
{
cin >> n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
{
cin >> D[i][j];
maxsum[i][j] = -1; //设一个初始值
}
cout << MaxSum(1,1) << endl;
}
终于,可以AC啦。
但是本文的重点是下面:
★★★★★★★3.递归改递推
虽然记忆递归可以求出来,但是速度还是相对递推较慢,递推也是只要计算n(n+1)/2就好了,而且式子简单。我说一下递推简单的思路:
很容易,我们可以求出最底层到最底层的大小,就是数组D[ i ][ j ]本身,所以,我们把它赋值在maxsum[ i ] [ j ] 数组中。
然后动态的从底层向上层计算,先求出两个数的最大值,然后与D [ i ][ j ]相加,就是(i,j)到底层的最大值。下面我们通过图画演示来做出来。
经过n-1轮迭代后,得出下面的图:
然后看代码说话:
#include <iostream>
#include <algorithm>
#define N 101
using namespace std;
int D[N][N];
int maxsum[N][N];
int n;
int main()
{
cin >> n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin >> D[i][j];
for(int j=1;j<=n;j++)
maxsum[n][j] = D[n][j];
for(int i=n-1;i>=1;i--)
{
for(int j=1;j<=i;j++)
{
int x = maxsum[i+1][j];
int y = maxsum[i+1][j+1];
maxsum[i][j] = max(x,y)+D[i][j];
}
}
cout << maxsum[1][1] << endl;
return 0;
}
4.重点终于讲完啦,但是还可以对本题加以改进。比如本题的 maxsum数组,是不是多余呢,可以用D[ n ]行的数据来替代,最后所有的值都在D[ n ] 行更新,不占用空间。
所以,我们可以用一个指针来控制D[ n ],这样所有的更新操作,只在D[ n ] 这个数组里。
附上代码:
#include <iostream>
#include <algorithm>
#define N 101
using namespace std;
int D[N][N];
int *maxsum;
int n;
int main()
{
cin >> n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin >> D[i][j];
maxsum = D[n];
for(int i=n-1;i>=1;i--)
for(int j=1;j<=i;j++)
{
maxsum[j] = max(maxsum[j],maxsum[j+1])+D[i][j];
}
cout << maxsum[1] << endl;
return 0;
}
总结:
由于水平有限,描述不是太清楚,希望大家多看教程学习,同时我们要理解本文第3个小结即可,剩下的太过抽象。