在班上某位acmer的推荐下,入门了以下网站:https://oi-wiki.org/(听说是由某位高中生大佬开发的!佩服~)
在里面有很多关于动态规划的知识点,以及各种不同动态规划类型的题目。
就我而言,我觉得动态规划其实就是把一些解题要用到的数据保存下来,避免重复计算。
至于动态规划的需要满足的三个条件:①最优子结构,②无后效性,③子问题重叠,大家就自行在里面观看吧,毕竟既然写在这里的题目,那包可以用动态规划来解决的~
其次,作为小白,我觉得动态规划的题目一般是先确认状态(即dp数组的含义),然后就是状态转移方程,有了这两个东西,基本上题目已经完成一大半了,剩下的就是代码的实现了。
话不多说,先从其引入的第一题开始:数字三角形
之前老师也在课堂上提到过这个题目,也简单讲解了一下思路,但过了几周也是全给我忘了。所以基本上是重新开始看这题目,不难也适合新手学习。首先看一下题目:
正常人看上去的第一眼就会想到自顶向下求解(包括我),但是这样暴力求解的话时间复杂度极高。而且在每一层的最优解并不一定是最底层的最优解,例如简单的7->3和7->8,虽然看上去7->8目前的值最大,但是仔细一看第三层会发现7->3->8会更好。所以我们要回到动态规划的本质,即如何划分子问题上来。
这个问题是要求一条路径到最底层(设为r)最大,那么反过来想如果知道了r层的所有数值,那么r-1层的数值全部遍历一遍,即加上左下角和右下角的值取最大,那么这样一层一层往上递进下去,到了第一层其实已经包含了所有路径中最大值了。即状态转移方程为:
dp[i][j]=max(dp[i+1][j]+w[i][j],dp[i+1][j+1]+w[i][j]);
//其中dp[i][j]表示第i行第j列的数到底层数值的最大值
至于为什么不能正着遍历,却要反过来遍历,跟动态规划的最优子结构有关,即求出的每一层的dp的最优解必须是最后结果的最优解,而正着遍历的话显然,每一层的最优解有可能只是局部最优解。
最后贴出代码:
#include<iostream>
using namespace std;
const int N =1001;
int w[N][N],dp[N][N];
int main(){
int r;
cin>>r;
for(int i=1;i<=r;i++){
for(int j=1;j<=i;j++){
cin>>w[i][j];
}
}
for(int i =1;i<=r;i++){dp[r][i]=w[r][i];}
for(int i =r-1;i>0;i--){
for(int j =1;j<=i;j++){
dp[i][j]=max(dp[i+1][j]+w[i][j],dp[i+1][j+1]+w[i][j]);
}
}
cout<<dp[1][1]<<endl;
return 0;
}