CCF-训练50题-NO.1-数塔问题

问题描述

给定一个数塔,如下图所示。在此数塔中,从顶部出发,在每一结点可以选择走左下或右下,一直走到底层。请找出一条路径,使路径上的数值和最大。


问题分析

求最大路径最简单的思维是:由于每个结点都有两个方向,于是最后有有限组方案。只要找出最大的方案即可。但是这样的方法过于复杂遇上较大的数据将会有可能陷入run-time error。但是如果我们每层每层看将会大大减轻工作量。第n-1行(n为最后一行)的每个数据计算为该数据加上他的两个子结点的后的最大值,这时其值表示的是从他这点出发可以走的最远路程。n-2行到第一行以此类推。

因此,从最后一行一直走到第一行的时候,第一行断的唯一结点表示的就是从该点出发的最远路程。最后求路径则是从第一行走回来,如果该结点的值减去初始值是等于其左边(或右边)的子结点的值,则表明路径的下一步是其左边(或右边)的子结点。

算法分析

采取先找最大生成树的方式,在查找遍历路径。最大生成树的查找方式采取动态规划的方式既涉及动态规划,就有其自己的状态方程

d[i][j]=a[i][j]+max{d[i+1][j+1],d[i+1][j]}

状态方程中a[m][n]是指第m行第n个元素;d[m][n]是指在第m行第n个元素这里到最底层的最大路径。每个结点的最大路径保存的是其子结点中有较大大路径的路径值加上其本身元素。通过这样的动态规划可以直接算出最大路径,而因为途中的最大路径的覆盖,得用倒推法算出路径。

代码:

#include <iostream>
using namespace std;
int main(){
	int n;
	cin>>n;
	int *a=new int[n*n];
	int *d=new int[n*n];
	int *line=new int[n]; 
	for (int i=0;i<n;i++){
		for (int j=0;j<i+1;j++){
			cin>>a[i*n+j];
		}
	}
	line[0]=a[0*n+0];
	for (int j=0;j<n;j++) {
		d[(n-1)*n+j]=a[(n-1)*n+j];
	}
	for (int i=n-2;i>=0;i--){
    	for (int j=0;j<=i;j++){
    		if (d[(i+1)*n+j]<d[(i+1)*n+(j+1)]) {
    		 d[i*n+j]=a[i*n+j]+d[(i+1)*n+(j+1)];
    	    }
    		else {
    		d[i*n+j]=a[i*n+j]+d[(i+1)*n+j];
            }
    	}
    }
    int pos=0;
    for (int i=0;i<n;i++){
    		if (d[(i+1)*n+pos]==(d[i*n+pos]-a[i*n+pos])){
    			line[i]=a[i*n+pos];
		}
    		else {
    			line[i]=a[i*n+(pos)];
    			pos++;
		}
    }
    	cout<<d[0*n+0]<<endl; 
        for (int i=0;i<n;i++) cout<<line[i]<<" ";
	return 0;
}

经验之谈:

这题目的核心在于将状态方程写出来,状态方程的核心就在于采用覆盖计算的方法先计算出最大的路径,再用逆向思维遍历出最大路径的各个结点。对于这样的题目要求我们对算法的状态方程有着很熟的把握。以及有着较好的顺逆向思维。因此,要多做这样的题目,这样才能对着代码有着更好的把握。






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值