动态规划--再论(数字三角形 poj1163)

在动态规划这里感觉还是有一些吃力,所以又要回到最初的研究方法,才感觉到这些才是平时的一些小问题:

下面将用最传统的方法进行讨论动态规划,其中的方法,见我的上一篇博客,这是用上一篇的博客的方法讨论;

对于数字三角形:

描述

7
3   8
8   1   0
2   7   4   4
4   5   2   6   5

(图1)


图1给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。

注意:路径上的每一步只能从一个数走到下一层上和它最近的左边的那个数或者右边的那个数。
输入
输入的是一行是一个整数N (1 < N <= 100),给出三角形的行数。下面的N行给出数字三角形。数字三角形上的数的范围都在0和100之间。
输出
输出最大的和。
样例输入

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

样例输出

30

对应着上一篇博客中的例子:

子问题:

 国王需要根据两个大臣的答案以及第9座金矿的信息才能判断出最多能够开采出多少金子。为了解决自己面临的问题,他需要给别人制造另外两个问题,这两个问题就是子问题。

 思考动态规划的第一点—-最优子结构:

 国王相信,只要他的两个大臣能够回答出正确的答案(对于考虑能够开采出的金子数,最多的也就是最优的同时也就是正确的),再加上他的聪明的判断就一定能得到最终的正确答案。我们把这种子问题最优时母问题通过优化选择后一定最优的情况叫做最优子结构

 思考动态规划的第二点—-子问题重叠:

 实际上国王也好,大臣也好,所有人面对的都是同样的问题,即给你一定数量的人,给你一定数量的金矿,让你求出能够开采出来的最多金子数。我们把这种母问题与子问题本质上是同一个问题的情况称为子问题重叠。然而问题中出现的不同点往往就是被子问题之间传递的参数,比如这里的人数和金矿数。

 思考动态规划的第三点—-边界:

 想想如果不存在前面我们提到的那些底层劳动者的话这个问题能解决吗?永远都不可能!我们把这种子问题在一定时候就不再需要提出子子问题的情况叫做边界,没有边界就会出现死循环。

 思考动态规划的第四点—-子问题独立:

 要知道,当国王的两个大臣在思考他们自己的问题时他们是不会关心对方是如何计算怎样开采金矿的,因为他们知道,国王只会选择两个人中的一个作为最后方案,另一个人的方案并不会得到实施,因此一个人的决定对另一个人的决定是没有影响的。我们把这种一个母问题在对子问题选择时,当前被选择的子问题两两互不影响的情况叫做子问题独立

首先需要将这几个问题考虑清楚:

1.最优子结构问题:

为了得到最大的和,每一个小子三角形,顶点都会向他的两个下顶点要,,,他们所储存的最大值;每一个小的三角形都这样;满足最优子问题;

2.子问题重叠:

 由于在同一个顶点上,会有两个三角形,所以每一次都会有不同的人找到同一个顶点,会有子问题重叠;

3.边界:

 当到达最后一行的时候,不能满足子三角形,所以只能是将最后一行作为自己的值了;dp[最后一行][j] = A[最后一行][j];

4.子问题独立:

 当求解每一个小三角形时,它与其他的小三角形无关,只是将自己的最大值求出来就是了;

 

5:选择个数:2

6.递推方程:dp[i][j] =max(dp[i+1][j],dp[i+1][j+1])+W[i][j]

7.备忘录的制作:这里暂时用一个二维数组dp[][]代替,可能会有优化,比如使用滚动数组,或者是map结构,再或者是其他,简单的储存结构

 不过一般使用滚动数组用的很多,节省空间;

这样就可以得到下面这个版本的动态规划,但是在空间上确实可能开的非常的大:

#include <iostream>
#define MAX 1000
using namespace std;


int main()
{
    int D[MAX][MAX];//储存结构数组
    int n;//行数
    int dp[MAX][MAX];//动归使用的数组;
    cin>>n;
    for(int i = 1;i<=n;i++)
        for(int j = 1;j<=i;j++)
            cin>>D[i][j];//输入数据
//    for(int i = 1;i<=n;i++){
//        for(int j = 1;j<=i;j++)
//            cout<<D[i][j]<<" ";
//        cout <<endl;
//    }
    for(int j = 1;j<=n;j++)
        dp[n][j] = D[n][j];//初始化dp数组;
    for(int i  = n-1;i>0;i--)//这个题目是逆向的求解;因为在边界条件是最后的一行的值,只能逆向;
        for(int j = 1;j<=i;j++){
            dp[i][j] = max(dp[i+1][j+1],dp[i+1][j])+D[i][j];
        }

//    for(int i = 1;i<=n;i++){
//        for (int j = 1;j<=i; j++){
//
//            cout <<dp[i][j]<<" ";
//
//        }
//        cout <<endl;
//    }
    cout <<dp[1][1]<<endl;

    return 0;
}

使用滚动数组:版本:

#include <iostream>
#define MAX 1000
using namespace std;


int main()
{
    int D[MAX][MAX];//储存结构数组
    int n;//行数
    int dp[2][MAX];//动归使用的数组;
    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++)
        dp[n%2][j] = D[n][j];//初始化dp数组;
    for(int i  = n-1;i>0;i--)//这个题目是逆向的求解;因为在边界条件是最后的一行的值,只能逆向;
        for(int j = 1;j<=i;j++){
            dp[i%2][j] = max(dp[(i+1)%2][j+1],dp[(i+1)%2][j])+D[i][j];
//
//            for(int i = 0;i<2;i++){
//                for (int j = 1;j<=n; j++)
//                    cout <<dp[i][j]<<" ";
//                cout <<endl;
//            }//测试
        }

    cout <<dp[1][1]<<endl;

    return 0;
}

还有一个终极的版本,不过不是很容易想到的:

int main()
{
    int D[MAX][MAX];
    int *nummax;
    int n;
    cin>>n;
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=i;j++){
            cin>>D[i][j];
        }
    }
    nummax = D[n];

    for(int i = n-1;i>=1;i--){
        for(int j = 1;j<=i;j++){
            nummax[j]=max(nummax[j],nummax[j+1])+D[i][j];
            cout <<nummax[j]<<" ";
        }
        cout <<endl;
    }

    cout <<nummax[1]<<endl;

    return 0;
}

ps:如果想知道动归的递推和递归之间的,可见我的另一篇的博客:http://blog.csdn.net/randyhe_/article/details/78460898

点赞