洛谷1216 数字三角形【dp】

经典例题:洛谷P1216 数字三角形

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。

每一步可以走到左下方的点也可以到达右下方的点。 

下图的黑色三角形是我们记忆化搜索的路径,我们想想,是不是可以不通过记忆化搜索就能得到这个黑色三角形??

最优性:设走到某一个位置的时候,它达到了路径最大值,那么在这之前,它走的每一步都是最大值。

-考虑这条最优的路径:每一步均达到了最大值 

最优性的好处:要达到一个位置的最优值,它的前一步也一定是最优的。 

-考虑图中位置,如果它要到达最优值,有两个选择,从左上方或者右上方的最优值得到:

所以从这里,定义动态规划(DP):只记录状态的最优值,并用最优值来推导出其他的最优值。

记录 F[i][j] 为第 i 行第 j 列的路径最大值,有两种方法可以推导:(两个分支两种状态,选取最大) 

@顺推:用 F[i][j] 来计算 F[i+1][j],F[i+1][j+1]。用当前项去计算后面的。“我这个状态的下一步去哪里”

@逆推:用 F[i-1][j],F[i-1][j-1] 来计算 F[i][j]。用前面的来计算当前项。“从什么状态可以到达我这里”(还没想好谁到谁)

这两种思考方法也是动态规划中最基本的两种方法,解决绝大部分DP我们都可以采用这样的方法。

//T2:数字金字塔-顺推(有点类似于记忆化搜索的思路)
//d数组储存顺序:记录从顶端向底部走的路径最优值(自顶向下)

#include<iostream>
#include<string.h> 
using namespace std;
int a[1005][1005];//储存数塔 
int d[1005][1005];//从该点到底端的最大数字和 
int main()
{
	int i,j,n,ans;
	while(cin>>n)
	{
		memset(d,-1,sizeof(d));
		for(i=0;i<n;i++)
			for(j=0;j<=i;j++)
			{
				cin>>a[i][j];
			}
		d[0][0]=a[0][0];
		for(int i=0;i<n-1;++i)
		for(int j=0;j<=i;++j)//d数组为最优值路径(黑色金字塔,a为源数据数组(紫色金字塔)
		{
		    //分别用最优值来更新左下方和右下方
		    d[i+1][j]=max(d[i+1][j],d[i][j]+a[i+1][j]);//和当前的f[i+1][j]比较
		    d[i+1][j+1]=max(d[i+1][j+1],d[i][j]+a[i+1][j+1]);//和当前的f[i+1][j+1]比较
		}
			//答案可能是最后一行的任意一个,所以把最后一行搜索一遍,最大的赋给ans
			ans=0;
			for(int i=0;i<n;i++)
			ans=max(ans,d[n-1][i]);
			cout<<ans<<endl;
//		for(int i=0;i<n;++i)
//		{
//			for(int j=0;j<=i;++j)
//			{
//				cout<<d[i][j]<<" ";
//			}
//			cout<<endl;
//		}
	}
	return 0;
}


//数字金字塔-逆推
//d数组储存顺序:记录从顶端向底部走的路径最优值(自顶向下)
#include<iostream>
#include<string.h> 
using namespace std;
int a[1005][1005];//储存数塔 
int d[1005][1005];//从该点到底端的最大数字和 
int main()
{
	int i,j,n,ans;
	while(cin>>n)
	{
		memset(d,-1,sizeof(d));
		for(i=0;i<n;i++)
			for(j=0;j<=i;j++)
			{
				cin>>a[i][j];
			}
		//逆推(自顶向下) 
		d[0][0]=a[0][0];
		for(int i=1;i<n;i++)
		{
		    d[i][0]=d[i-1][0]+a[i][0];//最左的位置没有左上方
		    d[i][i]=d[i-1][i-1]+a[i][i];//最右的位置没有右上方
		    for(int j=0;j<=i;j++)//在左上方和右上方取较大的
		    d[i][j]=max(d[i-1][j-1],d[i-1][j])+a[i][j];
		}
		//答案可能是最后一行的任意一个,所以把最后一行搜索一遍,最大的赋给ans
		ans=0;
		for(int i=0;i<n;i++)
		ans=max(ans,d[n-1][i]);
		cout<<ans<<endl;;	
	}
	return 0;
}

*转移方程:最优值之间的推导公式。 

@顺推:

F[i+1][j] = MAX (F[i][j] + a[i+1][j]); 

F[i+1][j+1] = MAX (F[i][j] + a[i+1][j+1]); 

@ 逆推: 

F[i][j] = MAX (F[i-1][j], F[i-1][j-1]) + a[i][j]; (注意!逆推时要注意边界情况! ) 
顺推和逆推本质上是一样的(复杂度一致);顺推和搜索的顺序类似;

而逆推则是将顺序反过来;顺推考虑的是“我这个状态的下一步去哪里” ,逆推的考虑的是“从什么状态可以到达我这里” 。

 同时在转移的过程中我们要时刻注意边界情况。

----------------------------------------------------------------------------------------------------------------------------------

我们还可以改变搜索顺序为自底向上(下面这两个算法基本一样,写法不一样):

//数字金字塔-逆推
//改变顺序:记录从底部向上走的路径最优值(自底向上)

//和之前的逆推区别:这样较自顶向下不需要判断边界,更加简单

#include<iostream>
#include<string.h> 
using namespace std;
int a[1005][1005];//储存数塔 
int d[1005][1005];//从该点到底端的最大数字和 
int main()
{
	int i,j,n,ans;
	while(cin>>n)
	{
		memset(d,-1,sizeof(d));
		for(i=0;i<n;i++)
			for(j=0;j<=i;j++)
			{
				cin>>a[i][j];
			}

		d[0][0]=a[0][0];
		for(int j=0;j<n;j++)
		d[n-1][j]=a[n-1][j];//备份最底部这一行
		
		//逆推过程:可以从左下方或右下方走过来;没有边界情况
		for(int i=n-2;i>=0;i--)
			for(int j=0;j<=i;j++)
		d[i][j]=a[i][j]+max(d[i+1][j+1],d[i+1][j]);//当前的+当前[i][j]左下方和右下方取较大
		//答案在金字塔顶端
		ans=d[0][0];
		cout<<ans<<endl;
	}
	return 0;
}
//(自底向上)
#include<string.h>
#include<iostream>
using namespace std;
int a[1005][1005];
int d[1005][1005];
int i,j,n,t; 
int dp(int i,int j)
{
	if(d[i][j]>=0)
	return d[i][j];
	else
	//写错了return d[i][j]+= a[i][j]+max(d[i+1][j],d[i][j+1]);
	{
		d[i][j]=a[i][j]+(i==n?0:max(dp(i+1,j),dp(i+1,j+1)));//(i==n-1?  也能AC

		return d[i][j];
	} 
}
int main()
{
//	int n; 有这个会导致样例输出7 
	while(cin>>n)
	{
		memset(d,-1,sizeof(d));
		for(i=0;i<n;i++)
		for(j=0;j<=i;j++)
		{
			cin>>a[i][j];
		}
//		两种输出方式 
//		dp(0,0);
//		cout<<d[0][0]<<endl;	
		cout<<dp(0,0)<<endl;
	}
	return 0;
}

*转移顺序:最优值之间的推导顺序 

一个小问题:在数字金字塔中,为什么能够使用动态规划 呢??答:因为有明确的顺序: 自上而下 ,也就是说,能划分成不同的阶段,这个阶段是逐步进行的,这和搜索顺序也是类似的,所以,只要划分好阶段, 从前往后推,与从后往前推都是可以的

转自:http://www.cnblogs.com/geek-007/p/7197045.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值