每日一题13:数字金字塔
观察如下数字金字塔。请写一个程序查找从最高点到底部任意点(数字金字塔共有,行)结束的路径,使路径经过数字的和最大,每一步只能走到左下方的点或右下方的点。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在如上样例中,7→3→8→7→5的路径产生了所有路径中最大的和7+3+8+7+5=30。
第1行输入,表示行数。
之后每行为数字金字塔特定行包含的整数(所有数非负且不大于100)。
一行,路径产生的最大的和。
直接搜索。设二维数组存放数字金字塔中的每个整数,存放最终结果即最大的和。问题要求从最高点按照规则走到最低点的路径的最大权值和,路径起终点固定,走法规则明确,可考虑使用搜索解决问题。
定义无返回类型递归函数 ,其中,表示当前已从走到,目前已走路径上的权值和为。
当时,到达递归出口,如果,则把更新为。当时,未到达递归出口,则向下一行两个位置行走,即递归执行和。
方法1实际上把所有路径都走了一边,由于每一条路径由步组成,每一步有左右两种选择,因此路径总数为,时间复杂度为,太大会超时。
方法1之所以会超时,是因为进行了重复搜索。当多次来到同一点时,可以直接调用来到这一点时的路径权值和,避免重复搜索。该方法称为记忆化搜索。
定义 表示从出发到终点的路径最大权值和,所以为所求答案。计算时考虑第一步是向左还是向右,就把所有路径分成两大类。
第一步向左的路径:从出发到终点的这类路径就被分成两个部分,先从到,要使得这种情况的路径权值和最大,第二部分从到终点的路径权值和也要最大,所以这一部分可以表示成。综上所述,第一步向左的路径最大权值和为。
第一步向右的路径:从出发到终点的这类路径就被分成两个部分,先从到,要使得这种情况的路径权值和最大,第二部分从到终点的路径权值和也要最大,所以这一部分可以表示成。综上所述,第一步向左的路径最大权值和为。分析方法与如上分类同理。
同样为了避免重复搜索,开设全局数组记录从每个点出发到终点路径的最大权值和,一开始全部初始化为-1表示未被计算过(即未到达过这一点)。在计算时,首先查询,如果,说明之前已被计算过,直接返回即可,否则计算出的值并存储在中,以便下次调用。
由于对于每个合法的都计算且仅计算过一次,而且是在内完成的,因此时间复杂度为,不超时。
方法3使用动态规划完成。方法2的记忆化搜索本质上与动态规划高度相似。
确定状态
题目要求从出发到底层路径的最大权值和,路径是由各个点串联而成,路径起点固定,终点和中间点相对不固定。因此定义表示从出发到达的路径最大权值和,并设······。
确定状态转移方程和边界条件
不考虑到的每一步是如何走的,只考虑最后一步是如何走的,根据最后一步是向左还是向右分成如下两种情况:
最后一步向左的路径:最后一步是从到,此类路径被分割成两部分,第一部分是从走到,第二部分是从走到,要计算此类路径的最大权值和,必须用到第一部分的最大权值和,此部分问题的性质与一样,就是,第二部分就是。两部分相加即得到此类路径的最大权值和为。
最后一步向右的路径: 最后一步是从到,此类路径被分割成两部分,第一部分是从走到,第二部分是从走到,要计算此类路径的最大权值和,必须用到第一部分的最大权值和,此部分问题的性质与一样,就是,第二部分就是。两部分相加即得到此类路径的最大权值和为。
的计算需要求出如上两种情况的最大值。综上所述,得到状态转移方程为。
与递归关系式需要终止条件一样,这里也需要对边界进行处理防止无限递归。计算时需要用到和,随着递归的深入,最终都要用到,的计算不可再用状态转移方程,而是应直接赋予一个特值。
所以根据上述得到边界条件为。
现在分析一下此动态规划的正确性,分析该解法是否满足使用动态规划的两个前提:
最优化原理:这个在分析状态转移方程时已经较为透彻,明显符合最优化原理。
无后效性:状态转移方程中,只会关心和的值,计算或时可能有多种不同的决策对应着最优值,选哪种决策对计算的决策没有影响,符合无后效性。
实现
由于状态转移方程就是递归关系式,边界条件就是递归终止条件,所以可以用递归来完成。递归存在重复调用,利用记忆化搜索可以解决这一点。记忆化搜索实现比较简单,且不会计算无用状态,但递归也会受到"栈的大小"和"递推+回归执行方式"的约束,另外记忆化实现调用状态的顺序是按照实际需求展开,没有大局规划,不利于进一步优化。
一种迭代法与分析边界条件相类似,计算用到状态和,这两个元素都在的上一行。也就是说,要计算第行的状态的值,必须先把第行的状态的值计算出来,再利用状态转移方程。可以先把赋值为,再从第2行开始按照行递增的顺序计算出每一行的有效状态。
对于题目所给样例,计算的结果如下表所示:
|
1 | 2 | 3 | 4 | 5 |
1 | 7 | / | / | / | / |
2 | 10 | 15 | / | / | / |
3 | 18 | 16 | 15 | / | / |
4 | 20 | 25 | 20 | 19 | / |
5 | 24 | 30 | 27 | 26 | 24 |
该动态规划时间复杂度为,不会超时。
#include
using namespace std;
int a[1005][1005],n,ans;
void dfs(int x,int y,int cur){
if(x==n){
ans=max(ans,cur);
return;
}
dfs(x+1,y,cur+a[x+1][y]);
dfs(x+1,y+1,cur+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];
dfs(1,1,a[1][1]);
cout<
方法2
#include
using namespace std;
int a[1005][1005],f[1005][1005],n;
int dfs(int x,int y){
if(f[x][y]==-1)
f[x][y]=(x==n?a[x][y]:a[x][y]+max(dfs(x+1,y),dfs(x+1,y+1)));
return f[x][y];
}
int main(){
memset(f,-1,sizeof(f));
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
dfs(1,1);
cout<
方法3
#include
using namespace std;
int a[1005][1005],f[1005][1005],n,ans;
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]=a[i][j]+max(f[i-1][j-1],f[i-1][j]);
for(int i=1;i<=n;i++)
ans=max(ans,f[n][i]);
cout<
你可能感兴趣的:(c++)