洛谷P1216——动态规划
题目解析
在面对这样的数字三角时,我们可能会想到的有的有两种方式解决这个问题,这里我们要去找到的是最大的路径,得到的将会是最优的决策,也就是全局最优。
当看到全局最优我们可能会想到的就有两种方法。
一、贪心法
当我们想要推出全局最优,使用贪心法,首先就要做出每一个小决策的最优方式,把大的问题简化为多个小问题,通过局部最优决策推导出全局最优决策。
这里的小问题,就是在做出每一次决策如何选择的问题,也就是当前m[i][j]的决策,是选择m[i+1][j]还是m[i+1][j+1]的问题,既然是最大,那么我们肯定是选择两个决策中较大的,然后再将当前的值相加。
推导过程:7——>8——>1——>7——>5 结果:28 可以看出每一个决策都是选择所谓最优的,但是得到的结果并不是全局最优。最优的应该是题目给出的路径,通过举反例,可以看出贪心法,并不能推导出全局最优,但是也差不多。
总结:再使用贪心法是,判断能否实现目标效果,通过举反例(其中一种方式)方法,便可分遍出是否可行,很显然,这里并不能得到我们想要的结果。
二、动态规划
在使用动态规划时,首先我们需要找到的是:状态转移方程,每一个节点的就是一个状态,在这里,可以通过逆序的方式在找到转移方程。注意:需要从倒数第二排开始进行计算。
状态转移方程:dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+m[i][j];
dp中的每一个节点都是一个状态,得到的是m[i][j]+m[i+1][j] 与 m[]i[j]+m[i+1][j+1]中较大的值,与贪心法不同的是,先算出结果,在来做决策,所以,每一个节点的值,就是以当前位置向下的最大路径,最终的结果直接输出目标节点的状态值就好了。
实现方法
首先要想到最后一排节点的最大路径,很显然其实就是他本身了,所以最后一排不用算,直接将m中的最后一排赋值给dp最后一排就好了;
for(int i=1;i<=n;i++){
dp[n][i]=m[n][i];
}
既然最后一排的最大路径已经得到,那么接下来直接从n-1排开始计算,调用转移方程:dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+m[i][j];
在计算当前节点时,计算的是结果最优路径,因为最后一排的值已经是最优路径了,所以,m[i][j]节点要加上的是结果最优路径,要加的是dp中的[i+1][j] [i+1][j+1]中最大的哪一个结果,从而得到的就是最优路径。
for(int i=n-1;i>=1;i--){//从n-1排开始计算
for(int j=1;j<=i;j++){//第i排的第j个元素
dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+m[i][j];//求这个节点的最大路径
}
}
dp中每个节点的状态
最后直接输出目标节点
cout<<dp[1][1]//目标节点就是从最上方,走到最下方的最优路径
完整代码
#include<iostream>
#include<algorithm>
using namespace std;
#define M 1050
int n;//n表示n行n列
int m[M][M]; //最大的矩阵,并不会全部使用到
int dp[M][M];
//输入函数
//注意这里的输入并不是需要完全输入,只需要输入一个三角就是
void test(int n){
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>m[i][j];
}
}
}
//输出函数 在程序中可以不用,这里用作验证dp中所有节点状态,或调试m中能否正确输入
void print(int n){
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cout<<dp[i][j]<<" ";
}
cout<<endl;
}
}
int main(){
cin>>n;
test(n);
for(int i=1;i<=n;i++){
dp[n][i]=m[n][i];
}
for(int i=n-1;i>=1;i--){
for(int j=1;j<=i;j++){
dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+m[i][j];
}
}
cout<<dp[1][1];
return 0;
}