一、动态规划定义
动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
二、动态规划解题基本思想
1.拆分问题
就是根据问题的可能性把问题划分成一步一步这样就可以通过递推或者递归来实现。关键就是这个步骤,动态规划有一类问题就是从后往前推到,有时候我们很容易知道:如果只有一种情况时,最佳的选择应该怎么做.然后根据这个最佳选择往前一步推导,得到前一步的最佳选择。
2.定义问题状态和状态之间的关系
就是前面拆分的步骤之间的关系,用一种量化的形式表现出来,类似于高中学的推导公式,因为这种式子很容易用程序写出来,也可以说对程序比较亲和(也就是最后所说的状态转移方程式)。
3.优化
比如我们找到最优解,我们应该把最优解保存下来,为了往前推导时能够使用前一步的最优解,在这个过程中难免有一些相比于最优解差的解,此时我们应该放弃,只保存最优解,这样我们每一次都把最优解保存了下来,大大降低了时间复杂度。
三、递归到动态规划的一般转换方法
如果该递归函数有n个参数,那么就定义一个n维数组,数组下标是递归函数参数的取值范围(也就是数组每一维的大小).数组元素的值就是递归函数的返回值(初始化为一个标志值,表明还未被填充),这样就可以从边界值开始逐步的填充数组,相当于计算递归函数的逆过程(这和前面所说的推导过程应该是相同的)。
四、动规解题的一般思路
1.将原问题分解为子问题
(注意:1,子问题与原问题形式相同或类似,只是问题规模变小了,从而变简单了; 2,子问题一旦求出就要保存下来,保证每个子问题只求解一遍)
2.确定状态
(状态:在动规解题中,我们将和子问题相关的各个变量的一组取值,称之为一个"状态",一个状态对应一个或多个子问题所谓的在某个状态的值,这个就是状态所对应的子问题的解,所有状态的集合称为"状态空间".我的理解就是状态就是某个问题某组变量,状态空间就是该问题的所有组变量) 另外:整个问题的时间复杂度就是状态数目乘以每个状态所需要的时间。
3.确定一些初始状态(边界条件)的值 (这个视情况而定,千万别以为就是最简单的那个子问题解,上面只是例子,真正实践动规千变万化)
4.确定状态转移方程 (这一步和第三步是最关键的 记住"人人为我"递推,由已知推未知)。
五、实例
1.最小路径和:https://leetcode-cn.com/problems/minimum-path-sum/
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
int dp[m][n];
dp[0][0]=grid[0][0];
for(int i=1;i<n;i++)
{
dp[0][i]=dp[0][i-1]+grid[0][i];
}
for(int i=1;i<m;i++)
{
dp[i][0]=dp[i-1][0]+grid[i][0];
}
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
dp[i][j]=min(dp[i][j-1],dp[i-1][j])+grid[i][j];
}
}
return dp[m-1][n-1];
}
};
2. 三角形最小路径和:https://leetcode-cn.com/problems/triangle/
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int n=triangle.size();
if(n==1) return triangle[0][0];
int dp[n][n];
dp[0][0]=triangle[0][0];
for(int i=1;i<n;i++) dp[i][0]=dp[i-1][0]+triangle[i][0];
for(int i=1;i<n;i++) dp[i][i]=dp[i-1][i-1]+triangle[i][i];
for(int i=2;i<n;i++)
for(int j=1;j<i;j++)
{
dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j];
}
//cout<<dp[3][0]<<" "<<dp[3][1]<<" "<<dp[3][2]<<" "<<dp[3][3]<<endl;
int minn=3000000;
for(int i=0;i<n;i++)
{
minn=min(minn,dp[n-1][i]);
}
return minn;
}
};
3.方格取数:https://www.luogu.com.cn/problem/P7074
#include <bits/stdc++.h>
using namespace std;
long long n,m,a[1005][1005],dp[1005][1005];
long long x1[1005][1005],x2[1005][1005];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) cin>>a[i][j];
dp[1][1]=a[1][1];
for(int i=2;i<=n;i++)
dp[i][1]=dp[i-1][1]+a[i][1];//初始化第一列
for(int j=2;j<=m;j++)
{
x1[1][j]=dp[1][j-1]+a[1][j];//第一行
x2[n][j]=dp[n][j-1]+a[n][j];//最后一行
for(int i=2;i<=n;i++)
{
x1[i][j]=max(x1[i-1][j],dp[i][j-1])+a[i][j]; //从上往下跑一遍
}
for(int i=n-1;i>=1;i--)
{
x2[i][j]=max(x2[i+1][j],dp[i][j-1])+a[i][j];//从下往上跑一遍
}
for(int i=1;i<=n;i++)
{
dp[i][j]=max(x1[i][j],x2[i][j]); //取最大值
}
}
cout<<dp[n][m];
return 0;
}