数字金字塔(DP)
题目
考虑在下面被显示的数字金字塔。
写一个程序来计算从最高点开始在底部任意处结束的路径经过数字的和的最大。
每一步可以走到左下方的点也可以到达右下方的点。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在上面的样例中,从7 到 3 到 8 到 7 到 5 的路径产生了最大和:30
Input
第一个行包含 R(1<= R<=1000) ,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
所有的被供应的整数是非负的且不大于100。
Output
单独的一行包含那个可能得到的最大的和。
Sample Input
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
Sample Output
30
题解
第一种方法—记忆化搜索:
每次搜索返回左下和右下中较大的那个,
并加一个记忆数组进行优化。
#include<stdio.h>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;
int meno[1001][1001],a[1001][1001],n;
int dfs(int x,int y){
if(meno[x][y]!=0){//如果此节点访问过,直接返回已算过的结果
return meno[x][y];
}if(x==n)meno[x][y]=a[x][y];
else{
meno[x][y]=max(dfs(x+1,y),dfs(x+1,y+1))+a[x][y];//返回左下右下两数的最大数
}return meno[x][y];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
scanf("%d",&a[i][j]);
}
}
printf("%d",dfs(1,1));
}
可是这题数据较大,这种方法过不了,但能过另一题,题目链接:
https://blog.csdn.net/keyixi/article/details/108085941
所以只能用:
第二种方法—动态规划之顺推:
这是个求最优解的题,可以用DP来做,速度会比搜索快。
从第二行第N行枚举每个阶段,每行再枚举1到i个数到顶点的最大值。
!!!状态转移方程:a[i][j]=max(a[i-1][j],a[i-1][j-1])+a[i][j]
2<=i<=n, 1<=j<=i
代码:
#include<stdio.h>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;
int a[1001][1001],n,ans;
int main(){
scanf("%d",&n);
scanf("%d",&a[1][1]);//最顶端的单独输入
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
scanf("%d",&a[i][j]);
a[i][j]+=max(a[i-1][j-1],a[i-1][j]);//状态转移方程
ans=max(a[i][j],ans);//ans为当前最大的和
}
}
printf("%d",ans);
}
代码短又能AC,DP真是好东西!
由于这是到十分经典的题,再加上顺推的成功,
所以我们可以再尝试一下:
第三种方法—逆推
顾名思义,逆推就是逆着递推,跟顺推差不多,
只是不能像顺推一样一边输入一边算,
而是全输入完再从n-1推到1。
代码:
#include<stdio.h>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;
int meno[1001][1001],a[1001][1001],n,ans;
int main(){
scanf("%d",&n);
scanf("%d",&a[1][1]);
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
scanf("%d",&a[i][j]);
}
}for(int i=n-1;i>=1;i--){//从第n-1行向上推
for(int j=1;j<=i;j++){
a[i][j]+=max(a[i+1][j],a[i+1][j+1]);//加上下一行左右两数的最大数
ans=max(ans,a[i][j]);
}
}
printf("%d",ans);
}
逆推也能过。