DP入门(1)——数字三角形问题
一、问题描述
如上图所示,有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数。现请你在此数字三角形中寻找一条从首行到最下行的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 - 99 。
二、问题分析
要求找出一条路径,它经过的数字之和最大。我们可以细化到每一步,每一次往下走都要选择较大的数。
于是可以得出下面的以下的伪代码:
if(当前行是最下行)
当前行到最下行的最大和 = 当前数字的值
else
当前行到最下行的最大和 = 下一行到最下行的最大和 + 当前数字的值
这样的伪代码实在是难读,因此我们需要用抽象的方法思考问题(即变量化):
- (i , j):当前的位置(状态)
- a(i , j):表示第 i 行的第 j 个数字 //i , j > 0
- d(i , j):从位置(i , j)到最下行的最大和 //状态(i , j)的指标函数,且原问题的解是d(1,1)
于是,上面的伪代码转化为:
if(i == n)
d(i,j) = a(i,j);
else
d(i,j) = max{d(i+1,j),d(i+1,j+1)} + a(i,j);
我们来看不同的状态之间是怎么转移的:从位置(i , j)出发有两种决策,①往左走,则走到(i+1 , j)后,将要求解d(i+1 , j);②往右走,则走到(i+1 , j+1)后,将要求解d(i+1 , j+1)。
由于可以在这两个决策中自由选择,所以应选择d(i+1 , j)和d(i+1 , j+1)中较大的那个。这一步正导出了所谓的状态转移方程:
- d(i , j)= max{d(i+1 , j), d(i+1 , j+1)} + a(i , j)
这个方程已经蕴含了最优质结构性质(全局最优解包含局部最优解)。即如果连“从(i+1 , j)或(i+1 , j+1)出发到最下行”这部分的和都不是最大的,加上a(i , j)之后肯定也不是最大的。
三、解题方式
1. 递归计算
int solve(int i,int j)
{
if(i == n) return a[i][j];
else return max(solve(i+1,j),solve(i+1,j+1)) + a[i][j];
}
分析:用直接递归的方法计算状态转移方程,效率往往十分低下。其原因是相同的子问题被重复计算。
2. 递推计算
for(int j=1;j<=n;j++) d[n][j] = a[n][j]; //最后一行
for(int i=n-1;i>=1;i--)
for(int j=1;j<=i;j++)
d[i][j] = max(d[i+1][j],d[i+1][j+1]) + a[i][j];
分析:i 是逆序枚举的,所以在计算d[i][j]前,它所需要的d[i+1][j]和d[i+1][j+1]都已经计算出来了。
提示:可以用递推法计算状态转移方程,递推的关键是边界和计算顺序。
3. 记忆化搜索
/* 第一部分:将d全部初始化为-1 */
memset(d,-1,sizeof(d));
/* 第二部分:编写递归函数 */
int solve(int i,int j)
{
if(d[i][j] != -1) return d[i][j]; //判断状态(i,j)是否已经被计算过
if(i == n) return d[i][j] = a[i][j];
else return d[i][j] = max(solve(i+1,j),solve(i+1,j+1)) + a[i][j];
}
分析:此程序是递归的,但是它同时把计算结果保存在数组d中。所以,千万别忘记在计算之后把它保存在d[i][j]中。此程序的方法称为记忆化,它虽然不像递推法那样显式地指明了计算顺序,但仍然可以保证每个结点只访问一次。
提示:根据C语言“赋值语句本身有返回值”的规定,可以把保存d[i][j]的工作合并到函数的返回语句中。
提示:可以用记忆化搜索的方法计算状态转移方程。当采用记忆化搜索时,不必事先确定各状态的计算顺序,但需要记录每个状态“是否已经计算过”。
四、解题代码
1. 递归计算
【第一次错误代码】
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstdlib>
using namespace std; int n;
int a[][]; int solve(int i,int j)
{
if(i==n) return a[i][j];
else return a[i][j] + max(a[i+][j],a[i+][j+]); //error
} int main()
{
cin>>n;
for(int i=;i<=n;i++){
for(int j=;j<=i;j++){
scanf("%d",&a[i][j]);
}
}
int ans = solve(,);
printf("%d\n",ans);
return ;
}
【第二次正确代码】
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstdlib>
using namespace std; int n;
int a[][]; int solve(int i,int j)
{
if(i==n) return a[i][j];
else return a[i][j] + max(solve(i+,j),solve(i+,j+));
} int main()
{
cin>>n;
for(int i=;i<=n;i++){
for(int j=;j<=i;j++){
scanf("%d",&a[i][j]);
}
}
int ans = solve(,);
printf("%d\n",ans);
return ;
}
- 分析:提交到poj1163显示TLE,显然递归求解不可行!
2. 记忆化搜索
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstdlib>
using namespace std; int n;
int a[][];
int d[][]; int solve(int i,int j)
{
if(d[i][j] != -) return d[i][j];
if(i==n) return d[i][j] = a[i][j];
else return d[i][j] = a[i][j] + max(solve(i+,j),solve(i+,j+));
} int main()
{
cin>>n;
for(int i=;i<=n;i++){
for(int j=;j<=i;j++){
scanf("%d",&a[i][j]);
}
}
memset(d,-,sizeof(d));
int ans = solve(,);
printf("%d\n",ans);
return ;
}
3. 递推计算
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstdlib>
using namespace std; int n;
int a[][];
int d[][]; int main()
{
cin>>n;
for(int i=;i<=n;i++){
for(int j=;j<=i;j++){
scanf("%d",&a[i][j]);
}
}
for(int j=;j<=n;j++) d[n][j] = a[n][j];
for(int i=n-;i>=;i--){
for(int j=;j<=i;j++){
d[i][j] = a[i][j] + max(d[i+][j],d[i+][j+]);
}
}
printf("%d\n",d[][]);
return ;
}
- 小结:这方面的代码还不够熟练,继续加强!
DP入门(1)——数字三角形问题的更多相关文章
- dp递推 数字三角形,dp初学者概念总结
数字三角形(POJ1163) 在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大.路径上的每一步都只能往左下或 右下走.只需要求出这个最大和即可,不必给出 ...
- 数字三角形 (DP入门)
7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 给出一个数字三角形.从三角形的顶部到底部有很多条不同的路径 ...
- 简单DP入门(一) 数字三角形
数字三角形
- 4829 [DP]数字三角形升级版
4829 [DP]数字三角形升级版 时间限制: 1 s 空间限制: 16000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 从数字三角形的顶部(如图, ...
- xbz分组题B 吉利数字 数位dp入门
B吉利数字时限:1s [题目描述]算卦大湿biboyouyun最近得出一个神奇的结论,如果一个数字,它的各个数位相加能够被10整除,则称它为吉利数.现在叫你计算某个区间内有多少个吉利数字. [输入]第 ...
- HDU 1176 免费馅饼 (类似数字三角形的题,很经典,值得仔细理解的dp思维)
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1176 免费馅饼 Time Limit: 2000/1000 MS (Java/Others) ...
- C++数字三角形问题与dp算法
题目:数字三角形 题目介绍:如图所示的数字三角形,要求从最上方顶点开始一步一步下到最底层,每一步必须下一层,求出所经过的数字的最大和. 输入:第一行值n,代表n行数值:后面的n行数据代表每一行的数字. ...
- 算法训练 数字三角形(DP)
问题描述 (图3.1-1)示出了一个数字三角形. 请编一个程序计算从顶至底的某处的一条路 径,使该路径所经过的数字的总和最大. ●每一步可沿左斜线向下或右斜线向下走: ●1<三角形行数≤100: ...
- hihoCoder#1037 : 数字三角形(DP)
[题目链接]:click here~~ 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 问题描写叙述 小Hi和小Ho在经历了螃蟹先生的任务之后被奖励了一次出国旅游的机会,于是他 ...
随机推荐
- React中setState同步更新策略
setState 同步更新 我们在上文中提及,为了提高性能React将setState设置为批次更新,即是异步操作函数,并不能以顺序控制流的方式设置某些事件,我们也不能依赖于this.state来计算 ...
- Webpack学习笔记九 webpack优化总结
webpack 优化笔记 webpack4 自带的优化包括 swingTree(摇摆树)和作用域提升 swingTree 比如入口文件 index.js引入通用方法 util, 里面有 10个方法, ...
- jquery mobile 移动web(1)
轻量级框架jQuery Mobile 所需文件 <link rel="stylesheet" href="jquery.mobile-1.1.2/jquery.mo ...
- poj 3694 Network : o(n) tarjan + O(n) lca + O(m) 维护 总复杂度 O(m*q)
/** problem: http://poj.org/problem?id=3694 问每加一条边后剩下多少桥 因为是无向图,所以使用tarjan缩点后会成一棵树并维护pre数组 在树上连一条边(a ...
- 吐血分享:QQ群霸屏技术教程2017(活跃篇)
热门词的群排名,在前期优化准备充分的情况下,活跃度不失为必杀技. 在<吐血分享:QQ群霸屏技术(初级篇)>中,我们提及到热门词的群排名,有了前面的基础,我们就可以进入深度优化,实现绝对的霸 ...
- 程序设计的SOLID原则
要想设计一个良好的程序,建议采用SOLID原则,若考虑了SOLID,可以使程序在模块内具有高内聚.而模块间具有低耦合的特点. SOLID原则包括5方面的内容: S---单责任原则(SRP) 一个模块只 ...
- C语言实现计算二进制数字1的个数
#include<stdio.h> #include<stdlib.h> int print_one_bits01(unsigned int value){ //0000 11 ...
- Str_turn
public class Str_turn { public static void main(String args[]) { String Str1 = new String("This ...
- Mult
public class MULT { public static void main(String args[]) { int k; for(int x=1;x<=9;x++) { for(i ...
- vuejs中的计算属性和监视
计算属性 1.在computed属性对象中定义计算属性的方法,在页面上使用{{方法名}}来显示计算结果 2.通过getter/setter实现对属性数据的显示和监视 3.计算属性存在缓存,多次读取只执 ...
