数字金字塔

数字金字塔

题目描述

在这里插入图片描述


解法一:搜索

首先考虑一下这一题可以使用贪心算法嘛?答案是不可以使用贪心算法。每次都贪心地选择最大的那个数,那么结果就是7->8->1->7->5,答案就是28,但是真正的应该是7->3->8->7->5,答最优解答案是30。因此,不能使用贪心算法。

我们可以使用二维数组来存储这个数字金字塔,从图形上来说,就是把图中的数字金字塔转换为直角三角形。从金字塔的某个点可以向左下方和右下方走,就等价于在这个直角三角形中是向下走和向右下走,这样就更利于搜索。设当前点为 ( x , y ) (x,y) (x,y),向下走就是 ( x + 1 , y ) (x+1,y) (x+1,y),向右下走就是 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1)

如下图所示:

在这里插入图片描述

我们设最优解答案是 a n s ans ans,设变量 c c c用来记录从上到下到达该点 ( x , y ) (x,y) (x,y)时的累加和(此时 c c c应该包括了点 ( x , y ) (x,y) (x,y)的权值 a [ x ] [ y ] a[x][y] a[x][y]了),那么当我们向下走时,点 ( x + 1 , y ) (x+1,y) (x+1,y)的累加和应该就是 c + a [ x + 1 ] [ y ] c+a[x+1][y] c+a[x+1][y];当我们向右下走时,点 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1)的累加和应该就是 c + a [ x + 1 ] [ y + 1 ] c+a[x+1][y+1] c+a[x+1][y+1]

写出核心代码如下:

void dfs(int x,int y,int c)
{
    if(x==n-1)	//走到了最后一层
    {
        if(c>ans)//比较此时走到最后一层的这个路径上的累加和c和最优解答案ans
            ans=c;
        return;	//回溯
    }
    dfs(x+1,y,c+a[x+1][y]);		//向下方走
    dfs(x+1,y+1,c+a[x+1][y+1]);	//向右下方走
}

画出的搜索树如下:

在这里插入图片描述


解法二:记忆化搜索

问题:从上到下的累加和是不能重复用的,但是从下到上的累加和是可以重复用的,如何理解这句话呢?

如下图解释,要注意,“从下到上的累加和是可以重复用的” 这个条件是我们可以使用记忆化搜索的前提,一般能使用记忆化搜索都会有这个性质。

在这里插入图片描述

理解如下图:

在这里插入图片描述

该记忆化搜索时,每一个节点只会被搜索一次,第一层有1个节点,搜索1次;第二层有2个节点,搜索2次, ⋯ \cdots ,第 n n n层有 n n n个节点,搜索 n n n次,因此时间复杂度为: 1 + 2 + ⋯ + n = n ( n + 1 ) 2 1+2+\cdots+n=\dfrac {n(n+1)}{2} 1+2++n=2n(n+1)也就是 O ( n 2 ) O(n^2) O(n2)。如果 n n n的数据规模不超过 1 0 4 10^4 104,一般都能过。

在这里插入图片描述

写出完整能Ac的代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=510;
int n;
int a[N][N];
int f[N][N];    //用来记录从下到上回溯返回时计算出的累加和
//记忆化搜索
int dfs(int x,int y)
{
    //如果这个点(x,y)已经被搜索过了,计算出来累加和了,那么我们就直接利用这个累加和就好了
    //而不用继续往下搜索(x,y)这个点了
    if(f[x][y]!=0)
        return f[x][y];
    //当走到最后一层时,从下到上记录累加和,那么此时它的累计和f[x][y]也就是它自身的权值a[x][y]
    if(x==n)
        f[x][y]=a[x][y];
    //否则就是它自身的权值+max(下方的累加和f[x+1][y],右下方的累加和f[x+1][y+1])
    else
        f[x][y]=a[x][y]+max(dfs(x+1,y),dfs(x+1,y+1));
    return f[x][y]; //返回搜索的节点(x,y)的结果
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            scanf("%d",&a[i][j]);
    dfs(1,1);   //从金字塔的顶端节点(1,1)开始搜索
    //由于是从下到上统计出来的累加和,那么总的结果就全部都累加到了
    //金字塔的顶端节点(1,1)上了,因此是答案就是f[1][1]
    printf("%d\n",f[1][1]); 
    return 0;
}

解法三:动态规划(顺推法)

我们用一个二维数组 f [ x ] [ y ] f[x][y] f[x][y]表示从上到下到达点 ( x , y ) (x,y) (x,y)时的累加和。分两种情况:从左上方走到当前点 ( x , y ) (x,y) (x,y),左上方的累加和是 f [ x − 1 ] [ y − 1 ] f[x-1][y-1] f[x1][y1];从右上方走到当前点 ( x , y ) (x,y) (x,y),右上方的累加和是 f [ x − 1 ] [ y ] f[x-1][y] f[x1][y]。设当前这个点 ( x , y ) (x,y) (x,y)的权值为 a [ x ] [ y ] a[x][y] a[x][y],那么点 ( x , y ) (x,y) (x,y)的累加和就是 f [ x ] [ y ] = m a x ( f [ x − 1 ] [ y − 1 ] , f [ x − 1 ] [ y ] ) + a [ x ] [ y ] f[x][y]=max(f[x-1][y-1],f[x-1][y])+a[x][y] f[x][y]=max(f[x1][y1],f[x1][y])+a[x][y]

如下图所示:

在这里插入图片描述

问题:为什么会有如下的初始化代码呢?

 for (int i = 0; i <= n; i++)
    {
        for (int j = 0; j <= i + 1; j++)//注意这里是j<=i+1而不是j<=i
            //因为有负数,所以应该将两边也设为-INF
            dp[i][j] = INF;//因为三角形中的数有可能是负数,所以dp[i][j]也有可能是负数
    }

在这里插入图片描述

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=510,INF=2e9;
int n;
int a[N][N];
int f[N][N];//表示从起点走到a[i][j]这个点的所经历的路径之和
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            scanf("%d",&a[i][j]);
    //对f数组进行初始化全部为负无穷
    //因为有负数,所以应该将边缘两边外面的位置都设为-INF
    //因此i从0开始而不是从1  j从0开始到i+1 而不是从1到i
    for(int i=0;i<=n;i++)
        for(int j=0;j<=i+1;j++)
            f[i][j]=-INF;
    //对第一层做特殊处理,金字塔顶端就只有一个数,那么这个数本身就是最大值
    f[1][1]=a[1][1];
    //从金字塔第二层开始枚举到第n层
    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=-INF;//注意这里三角形中的数有可能是负数,所以最大值也有可能是负数
    //从上到下已经把结果都累加到了最后一层了,因此,我们需要去比较最后一层里面的结果
    //然后找出最大的那个,就最优解
    //求出金字塔最后一层的最大值,比较最后一层每一列中的最大值,找出最大的那个最大值
    for(int j=1;j<=n;j++)
        ans=max(ans,f[n][j]);
    printf("%d\n",ans);
    return 0;
}

解法四:动态规划(逆推法)

我们用一个二维数组 f [ x ] [ y ] f[x][y] f[x][y]表示从下到上到达点 ( x , y ) (x,y) (x,y)时的累加和。分两种情况:从下方走到点 ( x , y ) (x,y) (x,y),下方的累加和是 f [ x + 1 ] [ y ] f[x+1][y] f[x+1][y];从右下方走到点 ( x , y ) (x,y) (x,y),右下方的累加和是 f [ x + 1 ] [ y + 1 ] f[x+1][y+1] f[x+1][y+1]

设当前这个点 ( x , y ) (x,y) (x,y)的权值为 a [ x ] [ y ] a[x][y] a[x][y],那么点 ( x , y ) (x,y) (x,y)的累加和就是 f [ x ] [ y ] = m a x ( f [ x + 1 ] [ y ] , f [ x + 1 ] [ y + 1 ] ) + a [ x ] [ y ] f[x][y]=max(f[x+1][y],f[x+1][y+1])+a[x][y] f[x][y]=max(f[x+1][y],f[x+1][y+1])+a[x][y]

在这里插入图片描述

用逆推法的好处就在于不用判断边界情况。从下到上累加,那么最终就会把累加和集中于起点,因此最后输出起点就好了。

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=510;
int n;
int a[N][N];
int f[N][N];//表示从起点走到a[i][j]这个点的所经历的路径之和
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            scanf("%d",&a[i][j]);
    //从下到上计算出累加和    
    for(int i=n;i>=1;i--)
        for(int j=i;j>=1;j--)
            f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j];
    //最终把累加和都集中于起点(1,1) 直接输出起点累加和就好了   
    printf("%d\n",f[1][1]);
    return 0;
}

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值