HDU2084 数塔 动态规划入门

HDU2084 数塔 动态规划入门

Problem Description

  在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
  有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
Alt
  已经告诉你了,这是个DP的题目,你能AC吗?

Input

  输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。

Output

  对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。

Sample Input

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

Sample Output

30

思路

  什么是动态规划

  动态规划将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。其中需要注意的是,动态规划将每个求解过的子问题的解记录下来,这样下一次遇到同样的问题时,就可以直接使用之前记录的解了。

  动态规划的递归写法 / 记忆化搜索

  当我们最初计算斐波那契数列是,我们曾这样计算:

int F(int n){
	if(n == 0 || n == 1) return 1;
	else return F(n-1)+F(n-2);
}

  这个递归中会有很多重复计算。当n为5时,F(5)需要F(3),而F(4)也需要F(3)才能得到结构,F(3)被计算了两次。当n很大时,会有更多的重复计算,实际复杂度会高达O(2^n)。
  为了避免重复计算,可以开一个一维数组dp,保存以及计算过的结果F[n],当dp[n] == -1时,表示还未计算过,代码如下:

int F(int n){
	if(n == 0 || n == 1) return 1;
	if(dp[n] != -1) return dp[n];
	else {
		dp[n] = F(n-1)+F(n-2);
		return dp[n];
	}
}

  于是当下次碰到需要计算相同的内容时,就能直接使用以前计算过的结果,省下无效计算,时间复杂度也降到了O(n)。这类重复出现的子问题也被称为重叠子问题(Overlapping Subproblems) ,一个问题必须拥有重叠子问题才能使用动态规划去解决。

  本例的实现——动态规划的递推写法

  对于本例,若尝试穷举所有路径,时间复杂度必然很大,也会产生很多重复。比如从9出发,经过9→12→6的路线来到6后,需要继续枚举6之后的路径,经过的9→15→6的路线来到6后,又要枚举6之后的路径。那不如把6之后的路径存起来?怎么存?
  不妨令dp[i][j]表示从第i行第j列出发到达底层的最大和,dp[3][2]既6到底层的最大和,dp[1][1]即为本题答案,那怎么求?
  首先注意到,最底层的dp值等于元素自身,那倒数第二层的dp值就是与其连接的结点中最大的最大和加上其本身,由此往上类推。具体来说,每个点要想知道自己的最大和,得先知道下一层和自己相连的那两个结点谁的最大和最大,再加上自己就成了。9只要知道12和15谁到底层的最大和最大,再加上自己就是自己的最大和了,那12和15咋知道自己的最大和呢?得知道下一层的最大和,继续往下,到了最底层,最底层的最大和就是自己(作为递推的边界),那就能推出上一层的最大和,再往上回推便知结果。
  令t[i][j]为自身值(即输入值),得到一个通式dp[i][j] = max(dp[i+1][j],dp[i+1][j+1]) + t[i][j],此式即为状态转移方程
  最主要是理解dp数组的含义。

代码

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 110;

int main(){
	int test,n,t[maxn][maxn],dp[maxn][maxn];//t数组记录数塔,dp记录各位置到底层的最大和
	scanf("%d",&test);
	while(test--){
		scanf("%d",&n);
		for(int i = 1;i <= n;i++){
			for(int j = 1;j <= i;j++){
				scanf("%d",&t[i][j]);
			}
		}
	
		for(int i = 1;i <= n;i++)
			dp[n][i] = t[n][i];//最底层的最大和即自己
		
		for(int i = n-1;i >= 1;i--){
			for(int j = 1;j <= i;j++){
				dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+t[i][j];//各位置到底层的最大和等于下一层连接的结点到底层的最大和加上自己 
			}
		} 
		printf("%d\n",dp[1][1]);
	}	
	return 0;
}

参考文献 《算法笔记》 胡凡.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值