DP入门——数字三角形问题
Posted GGBeng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DP入门——数字三角形问题相关的知识,希望对你有一定的参考价值。
一、问题描述
如上图所示,有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数。现请你在此数字三角形中寻找一条从首行到最下行的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于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. 递归计算
【第一次错误代码】
1 #include <iostream> 2 #include <cstdio> 3 #include <string> 4 #include <algorithm> 5 #include <cstdlib> 6 using namespace std; 7 8 int n; 9 int a[101][101]; 10 11 int solve(int i,int j) 12 { 13 if(i==n) return a[i][j]; 14 else return a[i][j] + max(a[i+1][j],a[i+1][j+1]); //error 15 } 16 17 int main() 18 { 19 cin>>n; 20 for(int i=1;i<=n;i++){ 21 for(int j=1;j<=i;j++){ 22 scanf("%d",&a[i][j]); 23 } 24 } 25 int ans = solve(1,1); 26 printf("%d\\n",ans); 27 return 0; 28 }
【第二次正确代码】
1 #include <iostream> 2 #include <cstdio> 3 #include <string> 4 #include <algorithm> 5 #include <cstdlib> 6 using namespace std; 7 8 int n; 9 int a[101][101]; 10 11 int solve(int i,int j) 12 { 13 if(i==n) return a[i][j]; 14 else return a[i][j] + max(solve(i+1,j),solve(i+1,j+1)); 15 } 16 17 int main() 18 { 19 cin>>n; 20 for(int i=1;i<=n;i++){ 21 for(int j=1;j<=i;j++){ 22 scanf("%d",&a[i][j]); 23 } 24 } 25 int ans = solve(1,1); 26 printf("%d\\n",ans); 27 return 0; 28 }
- 分析:提交到poj1163显示TLE,显然递归求解不可行!
2. 记忆化搜索
1 #include <iostream> 2 #include <cstdio> 3 #include <string> 4 #include <algorithm> 5 #include <cstdlib> 6 using namespace std; 7 8 int n; 9 int a[101][101]; 10 int d[101][101]; 11 12 int solve(int i,int j) 13 { 14 if(d[i][j] != -1) return d[i][j]; 15 if(i==n) return d[i][j] = a[i][j]; 16 else return d[i][j] = a[i][j] + max(solve(i+1,j),solve(i+1,j+1)); 17 } 18 19 int main() 20 { 21 cin>>n; 22 for(int i=1;i<=n;i++){ 23 for(int j=1;j<=i;j++){ 24 scanf("%d",&a[i][j]); 25 } 26 } 27 memset(d,-1,sizeof(d)); 28 int ans = solve(1,1); 29 printf("%d\\n",ans); 30 return 0; 31 }
3. 递推计算
1 #include <iostream> 2 #include <cstdio> 3 #include <string> 4 #include <algorithm> 5 #include <cstdlib> 6 using namespace std; 7 8 int n; 9 int a[101][101]; 10 int d[101][101]; 11 12 13 int main() 14 { 15 cin>>n; 16 for(int i=1;i<=n;i++){ 17 for(int j=1;j<=i;j++){ 18 scanf("%d",&a[i][j]); 19 } 20 } 21 for(int j=1;j<=n;j++) d[n][j] = a[n][j]; 22 for(int i=n-1;i>=1;i--){ 23 for(int j=1;j<=i;j++){ 24 d[i][j] = a[i][j] + max(d[i+1][j],d[i+1][j+1]); 25 } 26 } 27 printf("%d\\n",d[1][1]); 28 return 0; 29 }
- 小结:这方面的代码还不够熟练,继续加强!
以上是关于DP入门——数字三角形问题的主要内容,如果未能解决你的问题,请参考以下文章