观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。

输入:

第一个行包含R(1<= R<=1000),表示行的数目。

后面每行为这个数字金字塔特定行包含的整数。

所有的被供应的整数是非负的且不大于100。

输出:

单独的一行,包含那个可能得到的最大的和。

方法1:搜索

问题要求的是从最高点按照规则走到最低点的路径的最大的权值和,路径起点终点固定,走法规则明确,可以考虑用搜索来解决。

定义递归函数void Dfs(int x,int y,int Curr),其中x,y表示当前已从(1,1)走到(x,y),目前已走路径上的权值和为Curr。

当x=N时,到达递归出口,如果Curr比Ans大,则把Ans更新为Curr;否则向下一行两个位置行走,即递归执行Dfs(x+1,y,Curr+A[x+1][y])和Dfs(x+1,y+1,Curr+A[x+1][y+1])

#include <iostream>
using namespace std;
const int MAXN = 1005;
int A[MAXN][MAXN],F[MAXN][MAXN],N,Ans;
void Dfs(int x,int y,int Curr)
{if (x==N){if (Curr>Ans)Ans=Curr;return;}Dfs(x+1,y,Curr+A[x+1][y]);Dfs(x+1,y+1,Curr+A[x+1][y+1]);
}
int main()
{cin >> N;for(int i = 1;i <= N;i ++)for(int j = 1;j <= i;j ++)cin >> A[i][j];Ans =0;Dfs(1,1,A[1][1]);cout<<Ans<<endl;return 0;
}

该方法实际上是把所有路径都走了一遍,由于每一条路径都是由N-1步组成,每一步有“左”、“右”两种选择,因此路径总数为2N-1,所以该方法的时间复杂度为O(2N-1),超时。

方法2:记忆法搜索

方法1之所以会超时,是因为进行了重复搜索,如样例中从(1,1)到(3,2)有“左右”和“右左”两种不同的路径,也就是说搜索过程中两次到达(3,2)这个位置,那么从(3,2)走到终点的每一条路径就被搜索了两次,我们完全可以在第一次搜索(3,2)到终点的路径时就记录下(3,2)到终点的最大权值和,下次再次来到(3,2)时就可以直接调用这个权值避免重复搜索。我们把这种方法称为记忆化搜索。

记忆化搜索需要对方法一中的搜索进行改装。由于需要记录从一个点开始到终点的路径的最大权值和,因此我们重新定义递归函数Dfs。

定义Dfs(x,y)表示从(x,y)出发到终点的路径的最大权值和,答案就是Dfs(1,1)。计算Dfs(x,y)时考虑第一步是向左还是向右,我们把所有路径分成两大类:

①第一步向左:那么从(x,y)出发到终点的这类路径就被分成两个部分,先从(x,y)到(x+1,y)再从(x+1,y)到终点,第一部分固定权值就是A[x][y],要使得这种情况的路径权值和最大,那么第二部分从(x+1,y)到终点的路径的权值和也要最大,这一部分与前面的Dfs(x,y)的定义十分相似,仅仅是参数不同,因此这一部分可以表示成Dfs(x+1,y)。综上,第一步向左的路径最大权值和为A[x][y]+Dfs(x+1,y);

②第一步向右:这类路径要求先从(x,y)到(x+1,y+1)再从(x+1,y+1)到终点,分析方法与上面一样,这类路径最大权值和为A[x][y]+Dfs(x+1,y+1);

为了避免重复搜索,我们开设全局数组F[x][y]记录从(x,y)出发到终点路径的最大权值和,一开始全部初始化为-1表示未被计算过。在计算Dfs(x,y)时,首先查询F[x][y],如果F[x][y]不等于-1,说明Dfs(x,y)之前已经被计算过,直接返回 F[x][y]即可,否则计算出Dfs(x,y)的值并存储在F[x][y]中。

  #include <iostream>#include <algorithm>using namespace std;const int MAXN = 505;int A[MAXN][MAXN],F[MAXN][MAXN],N;int Dfs(int x,int y){if (F[x][y]==-1){if (x==N)F[x][y]=A[x][y];else F[x][y]=A[x][y]+max(Dfs(x+1,y),Dfs(x+1,y+1));}return F[x][y];}int main(){cin >> N;for(int i = 1;i <= N;i ++)for(int j = 1;j <= i;j ++)cin >> A[i][j];for(int i = 1;i <= N;i ++)for(int j = 1;j <= i;j ++)F[i][j] = -1;Dfs(1,1);cout << F[1][1] << endl;return 0;}

由于F[x][y]对于每个合法的(x,y)都只计算过一次,而且计算是在O(1)内完成的,因此时间复杂度为O()。

方法3:顺推法

方法2通过分析搜索的状态重复调用自然过渡到记忆化搜索,而记忆化搜索本质上已经是动态规划了。下面我们完全从动态规划的算法出发换一个角度给大家展示一下动态规划的解题过程,并提供动态规划的迭代实现法。
确定状态:

题目要求从(1,1)出发到最底层路径最大权值和,路径中是各个点串联而成,路径起点固定,终点和中间点相对不固定。因此定义F[x][y]表示从(1,1)出发到达(x,y)的路径最大权值和。最终答案Ans=max{F[N][1],F[N][2],...,F[N][N]}。
确定状态转移方程和边界条件:

不去考虑(1,1)到(x,y)的每一步是如何走的,只考虑最后一步是如何走,根据最后一步是向左还是向右分成以下两种情况:

向左:最后一步是从(x-1,y)走到(x,y),此类路径被分割成两部分,第一部分是从(1,1)走到(x-1,y),第二部分是从(x-1,y)走到(x,y),要计算此类路径的最大权值和,必须用到第一部分的最大权值和,此部分问题的性质与F[x][y]的定义一样,就是F[x-1,y],第二部分就是A[x][y],两部分相加即得到此类路径的最大权值和为F[x-1,y]+A[x,y];

向右:最后一步是从(x-1,y-1)走到(x,y),此类路径被分割成两部分,第一部分是从(1,1)走到(x-1,y),第二部分是从(x-1,y)走到(x,y),分析方法如上。此类路径的最大权值和为F[x-1,y-1]+A[x,y];

F[x][y]的计算需要求出上面两种情况的最大值。综上,得到状态转移方程如下:

F[x][y]=max{F[x-1,y-1],F[x-1][y]}+A[x,y]

与递归关系式还需要递归终止条件一样,这里我们需要对边界进行处理以防无限递归下去。观察发现计算F[x][y]时需要用到F[x-1][y-1]和F[x-1,y],是上一行的元素,随着递归的深入,最终都要用到第一行的元素F[1][1],F[1][1]的计算不能再使用状态转移方程来求,而是应该直接赋予一个特值A[1][1]。这就是边界条件。

综上得:

状态转移方程:F[x][y]=max{F[x-1][y-1],F[x-1][y]}+A[x,y]

边界条件:F[1][1]=A[1][1]

现在让我们来分析一下该动态规划的正确性,分析该解法是否满足使用动态规划的两个前提:

最优化原理:这个在分析状态转移方程时已经分析得比较透彻,明显是符合最优化原理的;

无后效性:状态转移方程中,我们只关心F[x-1][y-1]与F[x-1][y]的值,计算F[x-1][y-1]时可能有多种不同的决策对应着最优值,选哪种决策对计算F[x][y]的决策没有影响,F[x-1][y-1]也是一样。这就是无后效性。 
程序实现:

由于状态转移方程就是递归关系式,边界条件就是递归终止条件,所以可以用递归来完成,递归存在重复调用,利用记忆化可以解决重复调用的问题,方法二已经讲过。记忆化实现比较简单,而且不会计算无用状态,但递归也会受到“栈的大小”和“递推+回归执行方式”的约束,另外记忆化实现调用状态的顺序是按照实际需求而展开,没有大局规划,不利于进一步优化。

这里介绍一种迭代法。与分析边界条件方法相似,计算F[x][y]用到状态F[x-1][y-1]与F[x-1][y],这些元素在F[x][y]的上一行,也就是说要计算第x行的状态的值,必须要先把第x-1行元素的值计算出来,因此我们可以先把第一行元素F[1][1]赋为 A[1][1],再从第二行开始按照行递增的顺序计算出每一行的有效状态即可。时间复杂度为O()。

#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 1005;
int A[MAXN][MAXN],F[MAXN][MAXN],N;
int main(){cin >> N;for(int i = 1;i <= N;i ++)for(int j = 1;j <= i;j ++)cin >> A[i][j];F[1][1] = A[1][1];for(int i = 2;i <= N;i ++)for(int j = 1;j <= i;j ++)F[i][j]=max(F[i-1][j-1],F[i-1][j])+A[i][j];int ans =0;for(int i = 1;i <= N;i ++)ans = max(ans,F[N][i]);cout << ans << endl;return 0;}

方法4:逆推法

①贪心法往往得不到最优解:本题若采用贪心法则:13-11-12-14-13,其和为63,但存在另一条路:13-8-26-15-24,其和为86。

②动态规划求解:动态规划求解问题的过程归纳为:自顶向下的分析,自底向上计算。

其基本方法是:

划分阶段:按三角形的行,划分阶段,若有n行,则有n-1个阶段。

A.从根结点13出发,选取它的两个方向中的一条支路,当到倒数第二层时,每个结点其后继仅有两个结点,可以直接比较,选择最大值为前进方向,从而求得从根结点开始到底端的最大路径。

B.自底向上计算:(给出递推式和终止条件)

①从底层开始,本身数即为最大数;

②倒数第二层的计算,取决于底层的数据:12+6=18,13+14=27,24+15=39,24+8=32;

③倒数第三层的计算,取决于底二层计算的数据:27+12=39,39+7=46,39+26=65

④倒数第四层的计算,取决于底三层计算的数据:46+11=57,65+8=73

⑤最后的路径:13——8——26——15——24

C.数据结构及算法设计

①图形转化:直角三角形,便于搜索:向下、向右

②用三维数组表示数塔:a[x][y][1]表示行、列及结点本身数据,a[x][y][2]能够取得最大值,a[x][y][3]表示前进的方向——0向下,1向右;

③算法:

数组初始化,输入每个结点值及初始的最大路径、前进方向为0;从倒数第二层开始向上一层求最大路径,共循环N-1次;从顶向下,输出路径:究竟向下还是向右取决于列的值,若列的值比原先多1则向右,否则向下。

#include<iostream>
#include<cstring>
using namespace std;
int main()
{int n,x,y;int a[51][51][3];cout<<"please input the number of rows:";cin>>n;memset(a,0,sizeof(0));for (x=1;x<=n;x++)                         //输入数塔的初始值for (y=1;y<=x;y++){cin>>a[x][y][1];a[x][y][2]=a[x][y][1];a[x][y][3]=0;                          //路径走向,默认向下} for (x=n-1;x>=1;x--)for (y=1;y<=x;y++)if (a[x+1][y][2]>a[x+1][y+1][2])     //选择路径,保留最大路径值{ a[x][y][2]=a[x][y][2]+a[x+1][y][2]; a[x][y][3]=0; }else { a[x][y][2]=a[x][y][2]+a[x+1][y+1][2]; a[x][y][3]=1; }cout<<"max="<<a[1][1][2]<<endl;           //输出数塔最大值y=1;for (x=1;x<=n-1;x++)                      //输出数塔最大值的路径{cout<<a[x][y][1]<<"->";y=y+a[x][y][3];                         //下一行的列数}cout<<a[n][y][1]<<endl;
}

输入:

5               //数塔层数

13

11   8

12   7    26

6   14    15    8

12   7    13   24    11

输出结果:

max=86

13->8->26->15->24

数字金字塔(C++)相关推荐

  1. java编写数字金字塔_用JAVA写数字金字塔

    今年的蓝桥杯中我遇到了一道题是关于数字金字塔的,那时候在比赛时可能是各方面的因素有思路但是没有实现,直到今天回过头来看,其实只要思路正确了题目就会迎刃而解了,其实数字金字塔的的解题思路就是把金字塔分成 ...

  2. 使用双重循环,输出数字金字塔

    代码: package net.text0702;import java.util.Scanner;/*** @author Mr.Wang* 根据输入数字,输出数字金字塔:输入几就是几行,每行输出当 ...

  3. 1625 数字金字塔

    1625 数字金字塔 链接:http://codevs.cn/problem/1625/ USACO  时间限制: 1 s  空间限制: 128000 KB   题目描述 Description 考虑 ...

  4. 【动态规划】数字金字塔

    数字金字塔 Description 考虑在下面被显示的数字金字塔. 写一个程序来计算从最高点开始在底部任意处结束的路径经过数字的和的最大. 每一步可以走到左下方的点也可以到达右下方的点. 7 3 8 ...

  5. 本题要求实现函数输出n行数字金字塔。_练习5-3 数字金字塔 (15分)

    本题要求实现函数输出n行数字金字塔. 函数接口定义: void pyramid( int n ); 其中n是用户传入的参数,为[1, 9]的正整数.要求函数按照如样例所示的格式打印出n行数字金字塔.注 ...

  6. 信息学奥赛一本通 1258:【例9.2】数字金字塔

    [题目链接] ybt 1258:[例9.2]数字金字塔 [题目考点] 1. 记忆化搜索 2. 动态规划基本型 [解题思路] 思路1:一般深搜(非正确解) 每到一个位置,更新加和,向左下,右下两个方向搜 ...

  7. 信息学奥赛一本通(1258:【例9.2】数字金字塔)

    1258:[例9.2]数字金字塔 时间限制: 1000 ms         内存限制: 65536 KB 提交数: 20019     通过数: 11518 [题目描述] 观察下面的数字金字塔.写一 ...

  8. 数字金字塔(信息学奥赛一本通-T1258)

    [题目描述] 观察下面的数字金字塔.写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大.每一步可以从当前点走到左下方的点也可以到达右下方的点. 在上面的样例中,从13到8到26到15 ...

  9. java金字塔显示_java控制台输出数字金字塔示例分享

    /*Java *Author: NealFeng at oschina.net *License: GPLv2+ *Time: 2014/1/17 * *在控制台输出数字金字塔: *          ...

  10. CCF NOI1145 数字金字塔【DP】

    问题链接:CCF NOI1145 数字金字塔. 时间限制: 1000 ms  空间限制: 262144 KB 题目描述 观察下面的数字金字塔.写一个程序查找从最高点到底部任意处结束的路径,使路径经过数 ...

最新文章

  1. Linux系统的磁盘管理
  2. JWT –生成和验证令牌–示例
  3. 函数平移口诀_初三二次函数平移规律的口诀
  4. history模式 nginx配置_Vue history模式Nginx配置
  5. 跳跃回溯____寻找最长平台
  6. Java中如何在窗口加一行字,如何在java中实现读文件(一行一行地读)和写文件(一行一行地追加写)...
  7. Java 中的三大特性(超详细篇)
  8. 树莓派(zero w)——硬件介绍与系统开机
  9. ThinkPad E430 蓝牙驱动 BCM43142A0
  10. C++不规则窗体编程跳棋游戏实例!
  11. 洛谷 P1378 油滴扩展
  12. 三、共阳数码管的静态显示
  13. 一种无法用言语表达的爱——父爱
  14. The inferior stopped because it received a signal from the Operating system signal name: SIGSEGV
  15. Windows系统自带图标位置
  16. 自定义view实现涂鸦(画板)功能
  17. JSONObject.toBean() 把jsonobject转换成实体类
  18. c语言ntc程序,NTC热敏电阻温度计算以及C语言实现
  19. 爱盈利app推广专家相关介绍
  20. Python自动发短信

热门文章

  1. worksheet怎么读_worksheet是什么意思_worksheet的翻译_音标_读音_用法_例句_爱词霸在线词典...
  2. python中format函数用法简书_从Python安装到语法基础,这才是初学者都能懂的爬虫教程...
  3. hive LZO压缩
  4. HALO博客配置华为云OSS上传附件
  5. java 判断fibonacci_Java程序检查给定的数字是否是斐波纳契数
  6. AD中使叠在一起的元器件快速分开排列
  7. 【缓存】@CacheEvict
  8. 悠悠岁月,匆匆2014
  9. 电信增值短信平台模块清单
  10. 一个程序员应该怎样去学习和掌握计算机英语