题目描述
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5 在上面的样例中,从 7 ->3 -> 8 -> 7 -> 5 的路径产生了最大
输入格式
第一个行一个正整数 r ,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
输出格式
单独的一行,包含那个可能得到的最大的和。
样例 #1
样例输入 #1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5样例输出 #1
30提示
【数据范围】
对于 100% 的数据,1 <= r <= 1000,所有输入在 [0,100] 范围内。
题目翻译来自NOCOW。
USACO Training Section 1.5
IOI1994 Day1T1
思路
这是我自学动态规划的第一道经典例题(虽然我还是不知道他是线性DP还是区间DP),下面我为大家讲讲我的思路吧!
如何发现动态转移方程
发现动态转移方程的关键在于——找规律与对比。
首先这道题是让我们求一个道路的和最大,那么显然我们可以使用贪心来尝试。
但是使用贪心算法有个漏洞,就是当前方案眼光短浅,不一定是最优方案。就比如题目举出的例子,使用贪心线路如下:
7->8->1->7->5=28这显然不是最优解。
这时候就该祭出我们的秘诀了:递归思考,递归解决!
首先我们使用递归思考,可以推出公式:

那么使用递归解决的dfs函数就能打出来了:
递归dfs解决问题
int dfs(int x,int y)
{
if(x>n||y>n)return 0;
if(dp[x][y])return dp[x][y];
dp[x][y]=a[x][y];
int t1=dfs(x+1,y);
int t2=dfs(x+1,y+1);
return dp[x][y]+=max(t1,t2);
}这里使用记忆化搜索可以减少时间复杂度。
递归dfs代码实现
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int a[N][N];
int dp[N][N];
int n;
int dfs(int x,int y)
{
if(x>n||y>n)return 0;
if(dp[x][y])return dp[x][y];
dp[x][y]=a[x][y];
int t1=dfs(x+1,y);
int t2=dfs(x+1,y+1);
return dp[x][y]+=max(t1,t2);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
cin>>a[i][j];
}
}
cout<<dfs(1,1);
return 0;
}别急,这样写还是会超时,因为当n太大是就会超时。
那么该如何写呢?
递推解决问题
使用递推解决问题往往比递归的时间复杂度要低,因为递归会有概率将同一个部分的值算多次,而递推只用算一次就足够了。那怎么写递推呢?就是将递归的思路反过来即可。
递推解决问题代码实现
for(int i=n;i>=1;i--)
{
for(int j=1;j<=i;j++)
{
dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
}
}从最低端往上推,最后输出dp的第一行第一列的值即为正确答案。
递推解决问题完整代码
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int a[N][N];
int dp[N][N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
cin>>a[i][j];
}
}
//DP
for(int i=n;i>=1;i--)
{
for(int j=1;j<=i;j++)
{
dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
}
}
cout<<dp[1][1];
return 0;
}这样就能完美AC啦!
后记
第一次写关于动态规划的题解,点个赞吧!

文章介绍了使用动态规划解决寻找数字金字塔中最大和路径的问题,通过递归和记忆化搜索初探,然后优化为递推方法降低时间复杂度,最终得出正确答案。

810

被折叠的 条评论
为什么被折叠?



