洛谷 P1216 数字三角形 题解 || 动态规划

一、 [USACO1.5] [IOI1994]数字三角形 Number Triangles

洛谷:数字三角形

题目描述

观察下面的数字金字塔。

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

在上面的样例中,从 7 → 3 → 8 → 7 → 5 7 \to 3 \to 8 \to 7 \to 5 73875 的路径产生了最大权值。

输入格式

第一个行一个正整数 r r r ,表示行的数目。

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

输出格式

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

样例 #1

样例输入 #1

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

样例输出 #1

30

提示

【数据范围】
对于 100 % 100\% 100% 的数据, 1 ≤ r ≤ 1000 1\le r \le 1000 1r1000,所有输入在 [ 0 , 100 ] [0,100] [0,100] 范围内。

题目翻译来自NOCOW。

USACO Training Section 1.5

IOI1994 Day1T1

二、思考

如下图:给出两幅图,你会想到什么解决办法呢?

在这里插入图片描述
格子编号

错误思路

找到每行的最大值,依次向下查找
如果是按照这样的思路,会发现其他路径还有比当前路径之和大,代码运行失败!
在这里插入图片描述

思路分析

题意:从第一行的数开始,每次可以往左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来。如何走才能使得这个和尽量大?
[分析]

如果熟悉回溯法,你可能会立刻发现这是一个动态的决策问题:每次有两种选择一一左下或右下。如果用回溯法求出所有可能的路线,就可以从中选出最优路线。但和往常一样,回溯法的效率太低:一个n层数字三角形的完整路线有 2”条,当n 很大时回溯法的速度将让人无法忍受。

为了得到高效的算法,需要用抽象的方法思考问题:把当前的位置(,看成一个状态(还记得吗?),然后定义状态(i的指标函 d(i,为从格子i,出发时能得的最大和(包括格子(G)本身的值)。在这个状态定义下,原问题的解是 d(1,1)。

下面看看不同状态之间是如何转移的。从格子(i,j)出发有两种决策。如果往左走,则走到(i+1,j)后需要求“从(i+1,j),出发后能得到的最大和”这一问题,即 d(i+1,j)。类似地,往右走之后需要求解 d(i11)。由于可以在这两个决策中自由选择,所以应选择 d(i+1,j)和d(i+1,j+1)中较大的一个。
换句话说,得到了所谓的状态转移方程:

d(i,j)=a(i,j)+max{d(i+l,j),d(i+l,j+I)}

如果往左走,那么最好情况等于(i,)格子里的值a(i,与“从(1出发的最大总和”之和,此时需注意这里的“最大”二字。如果连“从(+1.出发走到底部”这部分的和都不是最大的,加上 a(i,之后肯定也不是最大的。这个性质称为最优子结构 (optimalsubstructure),也可以描述成“全局最优解包含局部最优解”。不管怎样,状态和状态转移方程一起完整地描述了具体的算法。

回溯法

在这里插入图片描述
如图,构造初始的数字三角形

在这里插入图片描述

动态规划演示—从底端开始

在这里插入图片描述
在这里插入图片描述

最优解数字三角形构造完成,最优解为:30

此处回溯找出路径
从第一行开始,减去她它下一行左边或右边最优解的较大值,并从较大值开始往下求解

在这里插入图片描述

三、Code

C语言

//数字三角形--模板 
#include <stdio.h>
int max(int a,int b) {
	return a>b?a:b;
}
void Write();
void Solution(int n,int triangle[][1001]);

int n;//行数
int triangle[1001][1001]; //三角形数组 (报错:RE,数组大小没与题目统一 ) 
//主函数 
int main() {
	Write();
	Solution(n,triangle);
	printf("%d",triangle[1][1]);
	return 0;
}

void Write() {
	scanf("%d",&n);
	for(int i = 1; i <= n; ++i) 
		for(int j = 1; j <= i; ++j) 
			scanf("%d",&triangle[i][j]);
}

//方案,核心代码 
void Solution(int n,int triangle[][1001]) {
	for(int i = n-1; i >= 1; --i)  //逆序枚举 
		for(int j = 1; j <= i; ++j)
			triangle[i][j] += max(triangle[i + 1][j],triangle[i + 1][j + 1]);
}

Java

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[][] arr = new int[n + 1][];
        for (int i = 1; i <= n; i++) {
            arr[i] = new int[i + 1];
            for (int j = 1; j <= i; j++) {
                arr[i][j] = sc.nextInt();
            }
        }
        for (int i = n - 1; i >= 1; i--) {
            for (int j = 1; j <= i; j++) {
                arr[i][j] += Math.max(arr[i + 1][j],arr[i + 1][j + 1]);
            }
        }
        System.out.println(arr[1][1]);
    }
}

C语言 数字三角形模板+回溯路径

//数字三角形模板+回溯路径 
#include <stdio.h>
int max(int a,int b) {
	return a>b?a:b;
}
int n; //数字三角形行数
int triangle[101][1001]; //数字三角形数组
int s[101]; //路径数组


//求解从三角形顶端到底端的路径所经过的数字和的最大值
void Solution(int n, int triangle[][1001]);
//求解路径数组
void TrackBack(int n, int triangle[][1001]);

int main() {
	scanf("%d",&n);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= i; ++j) {
			scanf("%d",&triangle[i][j]);
		}
	}
	//求解从三角形顶端到底端的路径所经过的数字和的最大值
	Solution(n, triangle);
	//写入
	printf("%d",triangle[1][1]);
	//输出最优解路径
	TrackBack(n, triangle);
	printf("\n");
	for (int i = 1; i <= n; ++i) {
		printf( "%d ",s[i] );
	}
	return 0;
}



void Solution(int n, int triangle[][1001]) {
	//从下往上计算
	for (int i = n - 1; i >= 1; --i) {
		for (int j = 1; j <= i; ++j) {
			triangle[i][j] += max(triangle[i + 1][j], triangle[i + 1][j + 1]);
		}
	}
}

void TrackBack(int n, int triangle[][1001]) {
	//从上往下计算
	int i, j, maxium;
	i = j = 1;
	while(i < n) {
		if (triangle[i + 1][j] > triangle[i + 1][j + 1]) {
			s[i] = triangle[i][j] - triangle[i + 1][j];
			i = i + 1;
			j = j;
		} else {
			s[i] = triangle[i][j] - triangle[i + 1][j + 1];
			i = i + 1;
			j = j + 1;
		}
	}
	s[i] = triangle[i][j];
}
运行结果
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
30
7 3 8 7 5

四、总结

动态规划的核心是状态、状态转移方程
状态转移方程:

d(i,j)=a(i,j)+max{d(i+l,j),d(i+l,j+I)}

此题的状态转移方程:

triangle[i][j] += max(triangle[i + 1][j],triangle[i + 1][j + 1]);
arr[i][j] += Math.max(arr[i + 1][j],arr[i + 1][j + 1]);

其他方法:记忆化搜索与递推;记忆化搜索要记得memset(d,-1,sizeof(d);把d全部初始化为-1;再写递归函数。 此处笔者不太熟悉,略讲。

注意:不要与背包问题混淆!!!

五、思路来源

算法分析与设计-数字三角形问题(动态规划)
洛谷:数字三角形
洛谷:采药

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
过河卒是一个典型的动态规划问题。首先,我们将整个棋盘看作一个二维数组,数组的每个元素表示到达该位置的路径数目。然后,我们根据题目给出的条件,逐步更新数组中的元素,直到计算出到达目标位置的路径数目。 具体的解题思路如下: 1. 首先,我们可以将马的位置设置为0,表示无法经过该位置。 2. 然后,我们根据马的位置,更新数组中的元素。对于二维数组中的每个位置,我们根据左边和上边的位置来计算到达当前位置的路径数目。具体地,如果左边和上边的位置都可以经过,那么到达当前位置的路径数目就等于左边和上边位置的路径数目之和。如果左边或上边的位置无法经过,那么到达当前位置的路径数目就等于左边或上边位置的路径数目。 3. 最后,我们输出目标位置的路径数目。 下面是洛谷p1002过河卒题解的C++代码: ```cpp #include <bits/stdc++.h> using namespace std; int main() { long long a[21][21]; int x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2; // 初始化数组,马的位置设置为0 for(int i=0; i<=20; i++) { for(int k=0; k<=20; k++) { a[i][k] = 1; } } a[x2][y2] = 0; // 根据马的位置更新数组中的元素 if(x2 >= 2 && y2 >= 1) a[x2-2][y2-1] = 0; if(x2 >= 1 && y2 >= 2) a[x2-1][y2-2] = 0; if(x2 <= 18 && y2 >= 1) a[x2+2][y2-1] = 0; if(x2 <= 19 && y2 >= 2) a[x2+1][y2-2] = 0; if(x2 >= 2) a[x2-2][y2+1] = 0; if(x2 >= 1) a[x2-1][y2+2] = 0; if(y2 >= 1) a[x2+2][y2-1] = 0; if(y2 >= 2) a[x2+1][y2-2] = 0; // 动态规划计算路径数目 for(int i=1; i<=20; i++) { for(int k=1; k<=20; k++) { if(a[i][k] != 0) { a[i][k] = a[i-1][k] + a[i][k-1]; } } } // 输出目标位置的路径数目 cout << a[x1][y1] << endl; return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值