动态规划的性质
动态规划常用来解决多阶段决策问题,能用动态规划求解的题目通常需要具有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 数字金字塔
问题描述
思路一:贪心
每次选取当前数字最大的路径
如上图,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;
}
思路四:DP(顺推法)
思路五:DP(逆推法)
课后作业 POJ 1163