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. 递归计算

【第一次错误代码】

 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
",ans);
27     return 0;
28 }
View Code

【第二次正确代码】 

 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
",ans);
27     return 0;
28 }
View Code
  • 分析:提交到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
",ans);
30     return 0;
31 }
View Code

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
",d[1][1]);
28     return 0;
29 }
View Code
  • 小结:这方面的代码还不够熟练,继续加强!
原文地址:https://www.cnblogs.com/xzxl/p/7492630.html