1258:【例9.2】数字金字塔
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 32578 通过数: 19357
【题目描述】
观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。
![](https://i-blog.csdnimg.cn/blog_migrate/25b0bed7a2eb28ed646b1e0329c53454.gif)
在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。
【输入】
第一个行包含R(1≤R≤1000),表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
所有的被供应的整数是非负的且不大于100。
【输出】
单独的一行,包含那个可能得到的最大的和。
【输入样例】
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
【输出样例】
86
题解
本题作为动态规划(DP)的入门题,主要来了解一下动态规划的思想。我们从题目中截取出这张图:
![](https://i-blog.csdnimg.cn/blog_migrate/25b0bed7a2eb28ed646b1e0329c53454.gif)
不难看出,在这样一种倒金字塔,或者说类树的结构中,出题者在引导我们用一种逆序的思想。
怎么逆序呢?具体来说,从倒数第二层开始,由于向下走只有一层,要取最大值,我们肯定取可以到达的两个点中较大值作为目的地,此时对于每个倒数第二层的点来说,从它到终点(最后一层)的最佳策略已经出现了,我们可以把选定的这个较大值累加到对应的倒数第二层的点上去,此时最后一层的点就没用了,就像消消乐一样,最后一层被消掉了,那我们就可以把原来的倒数第二层当作新的最后一层,继续操作,直到只剩下最后一层,此时金字塔顶端的值就是最长路径了。
以上一段是逆序思想的重点,细读几遍,便会发现,这其中已经包含了动态规划的三要素:阶段、状态、决策,其中阶段在本题中指的就是每一层为一个阶段,一层一层递进,状态,即当前最后一层的各个待选项,决策,在这里就是在两个中选择较大的累加。这里的决策,是DP的核心,写成代码就是(数字金字塔用dp数组表示):
dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);
而这一句话,就是大名鼎鼎的状态转移方程!当然,真正题目中的状态转移方程不可能都这么简单,但只要你想出了在两个阶段间转移状态的方法,那这道DP题大概就迎刃而解了。
有了它,代码就简单了,再次注意:逆序,逆序,逆序!
(当然有的同学一定要用正序也可以,不过通常正序的思路会非常难想,老师讲的也大都是逆序,我是我们年段写正序写的最好的,让我写正序,我也是先写逆序,在倒过来就是正序了,哦还有别忘了,以后大概率dp数组和保存数据的数组是分开的)
两段代码都放:
逆序:
#include<bits/stdc++.h>
using namespace std;
int dp[1005][1005];
int main(){
int n; scanf("%d",&n);
for(int i=1; i<=n; i++)
for(int j=1; j<=i; j++)
scanf("%d",&dp[i][j]);
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]); //状态转移方程
printf("%d",dp[1][1]); //输出即起点
return 0;
}
正序:(不推荐)
#include<bits/stdc++.h>
using namespace std;
int dp[1005][1005],ans;
int main(){
int n; scanf("%d",&n);
for(int i=1; i<=n; i++)
for(int j=1; j<=i; j++)
scanf("%d",&dp[i][j]);
for(int i=2; i<=n; i++)
for(int j=1; j<=i; j++)
dp[i][j]+=max(dp[i-1][j-1],dp[i-1][j]); //和逆序相反的状态转移方程,可以自己思考一下
for(int i=1; i<=n; i++) ans=max(ans,dp[n][i]); //正序的最大值保存在最后一行,一个个比较
printf("%d",ans);
return 0;
}
同样地,众所周知,这类(简单的,难的没试过)动态规划问题也可以用记忆化搜索解但搜索毕竟是递归,好理解一些,但栈调用的内存大,所以还是慢慢的都改用DP吧。
码字不易,关注点赞收藏支持一下吧!