Dynamic Programming从入门到放弃 —— 第一章:数字三角形模型

本文详细介绍了动态规划的概念及其应用于解决数字金字塔问题的多种方法,包括最优子结构和无后效性两个关键性质。通过递推、记忆化搜索等策略,展示了如何设计状态和状态转移方程来找到从最高点到底部的最大路径和。同时,通过实例分析了贪心策略的错误,并提供了四种不同的解决方案,包括顺推和逆推的递推方法。
动态规划的性质

动态规划常用来解决多阶段决策问题,能用动态规划求解的题目通常需要具有2个特点:
1.最优子结构性质
一个大问题可以分解为若干小问题,大问题的最优解由小问题的最优解推导而来,小问题的最优解构成大问题的最优解
2.无后效性
一旦当前的状态确定,那么它的最优解就确定了,后续就不用关心它是怎样来的,之后大问题就只需调用它的值就可以
在这里插入图片描述

如何设计
  • 设计动态规划需要准备的:
    1、设计状态
    2、设计状态转移方程

  • 设计动态规划方法:
    1、递推(顺推 + 逆推)
    2、记忆化搜索(递归,易超时)

记忆化搜索

记忆化搜索本质上就是DP,只不过它是用递归(DFS)去实现的DP,在DFS的过程中,记忆化消除了重叠子问题
对于数字金字塔:
1、设计状态
Dfs(x,y)表示从(x,y)出发到终点的最大路径和。那么Dfs(1,1)就是我们所求的结果
2、设计状态转移方程

Dfs(x,y) = a[x][y] + max(Dfs(x + 1,y) , Dfs(x + 1,y + 1));

(x,y)到终点的最大路径和可以转换为,从(x,y)的左下角或者右下角到终点的最大路径和
3、记忆化
直接搜索会带来重叠子问题,使得问题复杂度过高,为指数级。所以可以用f[x][y](x,y)到终点的最大路径和保存起来,下次只需要直接调用即可。

例题1 数字金字塔

在这里插入图片描述
问题描述

查找从最高点到底部任意处结束的路径,使得路径和最大。找出路径和的最大值。注意:每个点只能够往左下方或者右下方行走。行数n(1<=n<=1000),每个数字的范围为非负且不大于100

思路一:贪心
每次选取当前数字最大的路径
在这里插入图片描述
如上图,13 + 11 + 12 + 14 + 13 = 63

贪心策略是否正确?
在这里插入图片描述
13 + 8 + 26 + 15 + 24 = 86,故贪心策略错误

思路二:搜索

dfs(x,y)//从(x,y)开始搜索
{
    return a[x][y] + max(dfs(x + 1,y),dfs(x + 1,y + 1));
}

思路三:记忆化搜索

首先将金字塔存入二维数组之中
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

int n;
const int N = 110;
int a[N][N];//用来存储金字塔
int f[N][N];//f[x][y]用来表示(x,y)到最底层的最大距离

//记忆化模板
int dfs(int x,int y)//dfs(5,1) = 12
{
    if (f[x][y] != -1) return f[x][y];//只要f[x][y]更新过就可以直接取值
    if (x == n) f[x][y] = a[x][y];
    else f[x][y] = a[x][y] + max(dfs(x + 1,y),dfs(x + 1,y + 1));
    return f[x][y];
}

int main()
{
    cin >> n;
    for (int i = 1;i <= n;i++)//枚举行
        for (int j = 1;j <= i;j++)//是第几行就有几列
            scanf("%d",&a[i][j]);
    memset(f,-1,sizeof f);//将f数组初始化为-1
    dfs(1,1);//从(1,1)开始搜索到底部的最大距离
    cout << f[1][1];
    return 0;
}

输入
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
输出
86

思路四:递推(顺推法)
1、设计状态
定义f[x][y]为从(1,1)出发到(x,y)的路径的最大路径和。
那么,答案为ans = max{f[n][1],f[n][2],… …,f[n][n]}

2、状态转移方程

f[x][y] = max{f[x-1][y-1] , f[x-1][y]} + a[x][y]

3、边界条件:f[1][1] = a[1][1]

#include <iostream>

using namespace std;

const int N = 110;
int a[N][N];//用来存储三角形
int f[N][N];//f[x][y]:表示(1,1)到(x,y)的最长距离
int n;

int main()
{
    cin >> n;
    for (int i = 1;i <= n;i++)
        for (int j = 1;j <= i;j++)
            cin >> a[i][j];//初始化三角形
            
    f[1][1] = a[1][1];//顺推:从三角形上边往下边推
    for (int i = 2;i <= n;i++)
        for (int j = 1;j <= i;j++)
            f[i][j] = max(f[i - 1][j],f[i - 1][j - 1]) + a[i][j];
    
    int ans = -1;//找到f[n][1],f[n][2],...,f[n][n]中的最大值
    for (int i = 1;i <= n;i++) 
        if (f[n][i] > ans)
            ans = f[n][i];
    cout << ans;
    return 0;
}

思路五:递推(逆推法)
1、设计状态
定义f[x][y]为从(x,y)出发到终点(第n行)的路径的最大路径和。那么,答案为f(1,1)

2、状态转移方程

f[x][y] = max{f[x+1][y] , f[x+1][y+1]} + a[x][y]

3、边界条件

f[n][i] = a[n][i];(1 <= i <= n)
#include <iostream>

using namespace std;

const int N = 110;
int a[N][N];//用来存储三角形
int f[N][N];//f[x][y]:表示从(x,y)出发到达终点的最长距离
int n;

int main()
{
    cin >> n;
    for (int i = 1;i <= n;i++)
        for (int j = 1;j <= i;j++)
            cin >> a[i][j];//初始化三角形
            
    for (int i = 1;i <= n;i++) f[n][i] = a[n][i];//先写边界条件
    for (int i = n - 1;i >= 1;i--)//逆推:从三角形下边往上边推
        for (int j = 1;j <= i;j++)
            f[i][j] = max(f[i + 1][j],f[i + 1][j + 1]) + a[i][j];
            
    cout << f[1][1];
    return 0;
}

课后作业 POJ 1163

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值