【郭炜老师】【动态规划】1.POJ 1163 The Triangle

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个小结即可,剩下的太过抽象。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值