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

被折叠的 条评论
为什么被折叠?



