问题描述
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
(图一)
图一表示一个5行的数字三角形。假设给定一个n行数字三角形,计算出从三角形顶至底的一条路径,使该路径经过的数字总和最大。
每一步只能由当前位置向左下或右下。
输入格式
你的程序要能接受标准输入。第一行包含一个整数T,表示总的测试次数。
对于每一种情况:第一行包含一个整数N,其中1 < N < 100,表示三角形的行数。
接下来的N行输入表示三角形的每一行的元素Ai,j,其中0 < Ai,j < 100。
输出格式
输出每次测试的最大值并且占一行。
输入样例
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例
30
详解
dp三步走:
①定义dp方程的含义 定义是dp的重点,但也有迹可循,一般是定义二阶的dp【i】【j】
此题dp方程的含义可以定义为走到第i行第j列时的最大和是多少②找出dp方程(或称状态转移方程) 也就是找出递推式,我们先手写模拟解题的过程:
题目是说从上往下,但这样不好分析,因为从上往下分析,最下面有n个出口。
所以我们从下往上分析可以很明显的知道更新完整个矩阵之后,答案就是dp[0][0]
从题目倒数第二行开始,想一下2怎么更新?,是不是从它正下和右下选出最大数再加上他本身,所以2更新为 2 + 5 = 7.
那7怎么更新呢,是不是从它正下的5和右下的2选出最大数是5再加上它本身的7得到12
选最大值这个动作怎么用代码表示?可以抽象为max(正下的数,右下的数) 所以手推模拟后发现dp方程是dp[i][j] = max( dp
[i+1] [j] , dp [i+1] [j+1]) (0 <= i <= n -1,0 <= j <=
i)(i表示行,j表示列,都是从0开始) 有很多人发现了我们没有从最后一行开始往上推,而是从倒数第二行往上推的
这就涉及到我们下面要讲的初始边界确定③确定初始边界状态 因为最后一行下面没有路了,所以最后一行的值不用更新就是他们本身, 则初始状态就是当i = n - 1时,a[i][j]
= 他们本身(i表示行,j表示列,都是从0开始)
自顶向下求解
网上大部分代码都是从最后一层向上动态规划求解,对于初学者可能不太友好,这里给出一种从上往下的动态求解方式。(从底向上代码请往下看。)
#include<iostream>
using namespace std;
#include<bits/stdc++.h>
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
int a[n][n];
for(int i=0;i<n;i++)
{
for(int j=0;j<=i;j++)
cin>>a[i][j];
}
int dp[n][n]; //动态规划三角形,每个位置代表之前的最大和
memset(dp,0,sizeof(dp));
dp[0][0]=a[0][0]; //最顶部和就是三角形第一个数
for(int i=1;i<n;i++)
{
for(int j=0;j<=i;j++)
{
dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+a[i][j]; //求和
}
}
//所有的通路之和都在最后一层,现在遍历找最大值
int ma=0;
for(int i=0;i<n;i++)
{
ma=max(dp[n-1][i],ma);
}
cout<<ma<<endl;
}
return 0;
}
自底向上求解
#include<iostream>
#include<algorithm>
using namespace std;
int dp[110][110];
int main(){
int T,n;
cin >> T;
while(T--){
cin >> n;
//输入部分
for(int i = 0;i < n;i++)
for(int j = 0;j <= i;j++)
cin >> dp[i][j];
//输入部分
for(int i = n - 2;i >= 0;i--)
//从倒数第二行开始往第一行更新值
for(int j = 0;j <= i;j++)
//当前行值不得超过列值
dp[i][j] += max(dp[i+1][j],dp[i+1][j+1]);
//当前的值等于正下(dp[i+1][j])和右下(dp[i+1][j+1])的最大数加上他本身
cout << dp[0][0] << endl;
//输出结果
}
return 0;
}