【动态规划】动态规划解题的一般思路——以数字三角形为例

/*(POJ1163)数字三角形
*在数字三角形 中寻找一条从顶部到底边的路径,
* 使得路径上所经过的数字之和最大,路径上的每一步都只能往左下或者右下走。
*只需要求处这个最大和即可,不必给出具体路径
输入格式:
5 // 三角形行数。下面是三角形
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
要求输出最大和
*/

解题思路:
- 我们用一个二维数组D[r][j]储存第r行第j个数字,
- 函数MaxSum(i,j) 求从D(r,j)到底边的各条路径中,最佳路径的数字之和。

则可将此问题转化为典型的递归问题:
从D( i , j )出发,下一步只能走D( i+1, j ) 或者 D( i+1, j+1 )。

此时我们要求出从底边到 第i行 j列 的最佳路径MaxSum( i ,j ),
1. 先求出MaxSum(i+1, j)和MaxSum( i+1, j+1 ),
2. MaxSum( i , j )的值为其下第 i+1行的两个可进入的MaxSum的值中的较大者 + D[i][j]
3. 考虑边界,其最底行的MaxSum即为其本身

代码实现如下:

#include <iostream>
#include <algorithm>
#define MAX 101
using namespace std;

int N; //储存三角形的行数

int D[MAX][MAX];
int maxSum[MAX][MAX];
int MaxSum(int i,int j)  //第r行第j个数字(r,j从1开始) 
{
    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;
    return 0;
}

以这种方式,我们实现了动态规划最关键的第一步:
1.将原问题分解为子问题
子问题与原问题形式相同或相似,不过规模变小,只要子问题解决,原问题便解决。
例:在数字三角形中,要求MaxSum( i , j ) 则必须先求规模更小的 MaxSum( i+1, j ) 和MaxSum( i+1, j+1)并比较其大小。
子问题的解一旦求出就会被保存,所以每个子问题只需求解一次。
2.确定状态
在用动态规划解题时,我们往往和子问题相关的各个变量的一组取值,称为一个“状态”。一个“状态”对应与一个或多个子问题。某个“状态”下的“值”,就是这个状态所对应的子问题的解。
例:在数字三角形中 MaxSum(i,j)的一组取值(i , j)即为一个状态
同时,每个状态的值仅需计算一次,我们要用一个数组将所有计算的值储存起来
3. 确定初始状态(边界条件)
以数字三角形为例,初始状态就是底边数字,值就是底边数字。
4.确定状态转移转移方程
即如何一个或多个“值”已知的状态,求出另一个“状态”的值。
数字三角形的状态转移方程:
MaxSum[r][j] = { D [i] [j] ( 当 r = N ,边界情况) } 或 { Max ( MaxSum[ r + 1 ] [ j ] , MaxSum[ r + 1 ] [ j + 1] ) + D [ r ] [ j ] (其他情况) }

由递归到递推

#include <iostream>
#define MAX 101
int N;
int D[MAX][MAX];
int Max[MAX][MAX];
void CountMax()
{
    for(int i = 1;i <= N;++i )
        Max[N][i] = D[N][i];
    for(int i = N-1;i >= 1; --i)
        for(int j = 1; j <= i ; ++j)
            {
                int a = Max[i+1][j];
                int b = Max[i+1][j+1];
                int m = a > b ? a : b;
                Max[i][j] = m + D[i][j];
            }
}

using namespace std;
int main()
{
    cin >> N;
    for(int i = 1; i <= N; ++i)
        for(int j = 1; j <= i;++j)
            cin >> D[i][j];
    CountMax();
    cout << Max[1][1] << endl;
    return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值