从数塔顶层出发,每个结点可以选择向左走或向右走,要求一直走到塔底,使得走过的路径上的数值和最小。
例如对于下面这样的数塔1 2 3 4 5 6
按照 1 - 2 - 4 的路线走,可取得路径上的数值和的最小值为 7
每组输入的第一个行表示行数,最大不超过 1000 行。
后面每行为这个数塔特定行包含的正整数。这些正整数不大于 10000。
对于每组测试数据,输出一行答案。
3 1 2 3 4 5 6
7
今年课程的第一道dp(动态规划)题。
什么叫动态规划呢?动态规划是求多阶段决策问题的数学方法。这个算法的作用正如其名,是用来规划决策的,它经常被用于决策最优解的题目之中。
说了好多术语,我们用大白话来说说吧。如果有这么一种情况:你需要做出多次选择,并且要达到最好的情况。那么这就是多阶段决策问题。很多很多的题目都是这种类型,比如走迷宫,每次(最多)都有上下左右四个方向可以选择;再比如这题的数塔,每次你都要选择左边或者右边某一个分支。
然而,动态规划并不是只要有“抉择”就可以用的算法。它还需要满足以下几个条件
那么动态规划究竟是怎么实现的呢?
首先问一个最简单的问题,就拿题目给出的那个数塔来说吧,如果你知道:在数塔顶端的时候,选择左边那条路,无论如何怎么选,最小的结果只可能是6;选择右边那条路,无论如何怎么选,最小的结果只可能是8。那么,你会选择哪条路呢?很明显,题目要求的是最小值,我们肯定选择左边那条路,可问题来了——我们怎么知道选择左边那条路的“无论如何最小值”是多少呢?答案就是去算它的“无论如何最小值”。而计算左边的“无论如何最小值”的方法也是一样的,我们知道在2的那个地方,如果往左,无论如何最小的情况是4,而往右的无论如何最小情况时5,很明显,我们选择左侧,那么2所在位置的“无论如何最小值”就是4(左边)+2(2它自己)=6。而计算到最后一行的“无论如何最小值”就已经显而易见了,因为最后一行根本不需要选择了,他们的“无论如何最小值”就是他们本身。那么对于这个数塔,他们的无论如何最小值如下所示:
7 6 8 4 5 6
这个时候,顶端的7,就是最优的情况。
相信如果你反应比较快的话,就已经知道动规的原理了。我们这个“最优情况”的数组(也就是上图所示的数组)里的每一个值,都等于:它所可以选择的所有选项中的最优值 + 它本身。
比如第二行第一个,它本身是2,而它的两种选择:向左、向右,向左是4,向右是5,题目要求的最优是“最小”,所以选择较小的那个,2+4=6。再比如第一行第一个,它本身是1,向左是6,向右是8,所以选择向左,所以6+1=7。
所以对于这题,如果我们从原理入手去考虑,动态规划是“递归”(自己调用自己)的,也就是:从起点出发,每次都走最好的情况,但是刚开始并不知道哪个最好,就计算每个选项的值,这样自然就递归了。但如果我们从原理入手去考虑,动态规划是“迭代”(循环)的,也就是:从终点出发,计算出终点周围的情况,然后再向外推,直到到达起点为止。
这道题的动态规划的思路很简单,我们采用递归的方法,从数塔塔底往起点进发。
首先要开两个数组,一个存题目给的数据(命名为tower),而另外一个存对应位置的最优值(命名为dp)。
我们首先初始化已知的最优值——也就是最后一行的数据,因为最后一行根本不需要选择了,已经到底了,所以他们的最优值就是他们本身(最优值的公式是 min(左边最优值,右边最优值)+自己的数据, min表示二者中较小的一个, 很明显最后一行的前半项为0)。
然后我们开始从下往上计算。每一个位置的最优值 = min( dp[下一行][左边那个], dp[下一行][右边那个] ) + tower[本行][自己在的位置]。因为每个数据都是由下一行的dp数组以及tower数组决定的,tower数组是已知的,而下一行的dp数组已经计算完了,所以可以顺利的完成计算。
给出的参考代码中第一个for循环用来读取数据,第二个for循环用来初始化dp数组的最后一行,第三个for循环(两个for循环的嵌套)用来计算每个位置的值。
这种算法需要把每个位置都算一遍,即需要计算(1+H)*H/2
,其中H是塔高。而如果考虑选择方法的话,因为每次都有2种选择,所以一共有2^( H*(H-1)/2 )
种,很明显前者比后者算的次数要少非常多。
优化:题目告诉我们数塔最多有1000行,动态规划是需要开两个数组的,一个存数据本身,还有一个存最优值,也就是需要开两个1000x1000的数组。但是很明显,这是一个金字塔型的数据,如果用二维数组去存储,有一半的空间都被浪费了。所以这里我们选择用一维数组去存储,然后写一个函数trans,trans可以把一个二维坐标(第i行第j个数据)转化为一维坐标,这么做可以节省一半的内存空间。
参考代码:
// TSOJ-1059 数塔问题
#include
using namespace std;
int tower[500500],dp[500500];
int trans(int i, int j)
{
return i*(i+1)/2+j;
}
int main()
{
int hei;
while(cin>>hei)
{
for(int i=0;i<(1+hei)*hei/2;i++)
cin>>tower[i];
for(int i=0;i<hei;i++)
dp[trans(hei-1,i)] = tower[trans(hei-1,i)];
for(int i=hei-2;i>=0;i--)
for(int j=0;j<i+1;j++)
dp[trans(i,j)] = tower[trans(i,j)] + ((dp[trans(i+1,j)]>dp[trans(i+1,j+1)])?dp[trans(i+1,j+1)]:dp[trans(i+1,j)]);
cout<<dp[0]<<endl;
}
return 0;
}