蓝桥杯(数字三角形)------动态规划

题目描述

上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。

路径上的每一步只能从一个数走到下一层和它最近的左边的那个数或者右 边的那个数。此外,向左下走的次数与向右下走的次数相差不能超过 1。

输入描述

输入的第一行包含一个整数 N (1≤N≤100),表示三角形的行数。

下面的 N 行给出数字三角形。数字三角形上的数都是 0 至 100 之间的整数。

输出描述

输出一个整数,表示答案。

输入输出样例

示例

输入

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

输出

27

 这题跟我前面写过的蓝桥杯(数字三角形)-优快云博客不同之处就是多了一个要求(向左下走的次数与向右下走的次数相差不能超过 1),同样还跟我前面写过的另一题很像,如果你能搞懂这题求解矩阵最小路径和问题------动态规划-优快云博客,那么这题自然而然就会写啦,两题的解题思路大差不差哈哈哈。

解题思路   

  • 状态定义:dp[i][j] 表示从 (0,0) 到 (i,j) 的最大路径和。
  • 转移方程
    • 若在 (i,j),只能从上方 (i-1,j) 或左上角 (i-1,j-1) 到达。
    • i>0,j>0时,dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]) + a[i][j]。
  • 边界
    • 第一列:j=0,i>0时,dp[i][0] = dp[i-1][0] + a[i][0](只能从上面来)。
    • 对角线:i=j,i>0,j>0时,dp[i][0] = dp[i-1][j-1] + a[i][0](只能从左上角来)。
    • 起点:dp[0][0] = a[0][0]。
  • 目标: j(0~n-1)范围内,dp[n-1][j]最大且向左下走的次数与向右下走的次数相差不能超过1,就是最终的最大路径和。
  • 路径记录:用一个数组 prev[i][j] 记录到达 (i,j) 的前驱方向(“上”或“左上”),方便回溯路径。
  • 回溯路径:最后一行的每一个点都要回溯路径,根据 prev 数组逆推路径,最后就可以输出符合要求的最大路径和啦。

代码如下: 

#include<bits/stdc++.h>
using namespace std;
int main()
{
	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];//状态定义,dp[i][j]表示从 (0,0)到 (i,j) 的最大路径和
	dp[0][0]=a[0][0];//初始化dp 
	int prev[n][n];//方向数组,0表示从左上角到该点,1表示从正上方到该点 
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<=i;j++)
		{
			//若该点位于第一列
			if(j==0 && i>0)
			{
				dp[i][j]=dp[i-1][0]+a[i][j];
				prev[i][j]=1;//第一列上的点只能选择他正上方为前驱点 
			}
			
			//若该点位于对角线上 
			else if(i==j && i>0 && j>0)
			{
				dp[i][j]=dp[i-1][j-1]+a[i][j];
				prev[i][j]=0;//对角线上的点只能选择它左上角为前驱点 
			}
			
			//若该点不是 (0,0),也不在第一列也不在对角线上,就选择路径和更大的一个前驱点 
			else if(i>0 &&j>0)
			{
				int max=0;//前驱点路径和较大值 
				 
				//如果 (0,0)到正上方的点比到左上角的路径和更大 
				if(dp[i-1][j]>dp[i-1][j-1])
				{
					max=dp[i-1][j];
					prev[i][j]=1;//选择正上方为前驱点  
				}
				
				//反之	
				else
				{
					max=dp[i-1][j-1];
					prev[i][j]=0;//选择左上角为前驱点  
				}
					
				dp[i][j]=max+a[i][j];
			}
		}
	} 
	
	//用方向数组prev自底向上回溯,求出符合要求的一条路径
	//要求1:路径和最大
	//要求2:向左下走的次数与向右下走的次数相差不能超过 1
	
	int max=0;//记录符合要求的最大路径和 
	for(int k=0;k<n;k++)
	{
		int cnt0=0;//记录前驱点为左上角的次数,记住每一次回溯之前都要清零 
		int cnt1=0;//记录前驱点为正上方的次数,记住每一次回溯之前都要清零  
		int i=n-1;
		int j=k;
		while(1)
		{
			if(i==0 && j==0) break;//回溯到(0,0)结束 
			//若该点的前驱点在左上角,cnt0++ 
			if(prev[i][j]==0)
			{
				i--;
				j--;
				cnt0++;
			}
			//若该点的前驱点在正上方,cnt1++ 
			else if(prev[i][j]==1)
			{
				i--;
				cnt1++;
			}
		}
		 
		if(abs(cnt0-cnt1)<=1 && dp[n-1][k]>max)
			max=dp[n-1][k];
	}   
	
	//输出最终符合要求的最大路径和 
	cout<<max<<endl; 
	return 0; 
}

 希望能帮助到各位同志,祝天天开心,学业进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值