动态规划的定义
动态规划算法与分治法的思想类似,都是通过组合子问题的解来求原问题。我先来给大家介绍一下分治思想,分治思想就是把一个复杂的问题,分解为k个规模相同的子问题,如果还是无法解决,子问题又可以分为若干个相同规模的子问题求解,之后组合求原问题(难点)如二分法,归并排序,折半查找。至于动态规划:就是一个最优化问题,先将问题分解为子问题,并且对于这些分解的子问题自身就是最优的才能在这个基础上得出我们要解决的问题的最优方案(假设最优然后去求),要不然的话就能找到一个更优的解来替代这个解,得出新的最优自问题,这当然是和前提是矛盾的。同于 贪心算法,因为贪心算法是从局部最优来解决问题,而动态规划是全局最优的。用动态规划的时候不可能在子问题还没有得到最优解的情况下就做出决策,而是必须等待子问题得到了最优解之后才对当下的情况做出决策,所以往往动态规划都可以用 一个或多个递归式来描述。而贪心算法却是先做出一个决策,然后在去解决子问题。这就是贪心和动态规划的不同。
–摘自剑锋OI博文
- 动态规划求解问题的三个基本条件
1)无后效性: 动态规划要求已经求解的子问题不受后续阶段的影响,以保证对每一阶段的计算能够按顺序、不重复地执行。
2)最优子结构性质: 在动态规划算法求解问题的过程中,下一阶段的解应该能由前面各阶段子问题的最优解导出。
3)子问题重叠性: 动态规划所针对的问题还有另外一个显著的特征,即它所对应的子问题树中的子问题呈现大量的重复,称为子问题重叠性质。 - 用动态规划求解问题的一般思路
1)首先要确定子问题的状态,这是一个关键(即确定dp数组代表的含义是什么)。
2)确定状态转移方程(可用于计算各阶段同类子问题的计算公式)进而求解原问题。
动态规划和贪心类似,都没有固定的解题模板,只能通过多刷题,多分析来确定问题的状态转移方程(重点),进而求解问题。
下面我们通过做一些题目来进一步理解动态规划。
最长上升子序列问题
题目出自SDUTOJ
题解:
我们要求最长上升子序列的长度,自然要挨个数字来寻找,这时我们用dp[i]来表示用i个数字结尾的最长上升子序列的长度。
样例 1 7 3 5 9 4 8
dp[i] 1 2 2 3 3 …
dp[2]:
dp[2]=dp[1]+1;
dp[3]:
dp[3]=dp[1]+1;
dp[4]:
dp[4]=dp[1]+1;
dp[4]=dp[3]+1;
这时我们发现了什么规律呢?
没错从第一位数字开始寻找,找到一个比当前的数小的数就+1更新一下,直到遍历到当前数字。
我们就得到状态转移方程
dp[i]=max( dp[j] + 1, dp[i] ) 0<j<i;
#include <iostream>
using namespace std;
const int N=1000;
int a[N],dp[N];
int main()
{
int n;
int res=0;
cin>> n;
dp[0]=0;
a[0]=-1; //注意这里为什么要让a[0]=-1?还是说让a[0]小于某个数就行? 继续往下看
for(int i=1;i<=n;i++)
{
cin>>a[i];
for(int j=0;j<i;j++){
if(a[j]<a[i]){
//这里我们第一次发现了a数组的比较 j从0开始也就发现了上面关于a[0]的问题
//我们注意到a[i]任意一个数是大于等于0的而a[0]是负数 结合下面的代码我们发现其目的就是在于解决最小上升子序列的长度为1的情况
dp[i]=max(dp[i],dp[j]+1);
}
}
res=max(res,dp[i]);
}
cout<<res<<endl;
return 0;
}
shi最长公共子序列问题(LCS问题)
题目源自SDUTOJ
题解:
状态表示:我们用dp[i][j]表示数字A[1,i]与B[1,j] 的最长公共子序列长度。
分析:
当s1[i]=s2[j]时 dp[i][j]=d[i-1][j-1]+1; 因为相同时肯定是最长公共子序列长度+1;
当s1[i]!=s2[j]时 dp[i][j]=max(dp[i][j-1],dp[i-1][j]) 举个例子:
ABC A
ABD C
可得dp[4][4]=dp[3][4];
#include <iostream>
#include <string.h>
using namespace std;
const int N=505;
int dp[N][N];
char s1[N],s2[N];