【问题描述】
考虑在下面被显示的数字金字塔。
写一个程序来计算从最高点开始在底部任意处结束的路径经过数字的和的最大(小)。每一步可以走到左下方的点也可以到达右下方的点。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在上面的样例中,从7 到 3 到 8 到 7 到 5 的路径产生了最大和:30
【输入文件】
第一个行包含 R(1<= R<=1000) ,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
所有被供应的整数是非负的且不大于100。
【输出文件】
单独的一行包含那个可能得到的最大的和。
【输入样例】
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
【输出样例】
30
【提交链接】
http://poj.org/problem?id=1163
【问题分析】
这个问题是学习动态规划最简单最经典的问题,说它经典是因为它的阶段、状态、决策都十分明显。
刚看到题目觉得没有入手点,连怎么储存,描述这个金字塔都是问题,看输入样例发现:数字金字塔可以变成像输入样例那样的下三角,这样可以用一个二维数组a储存它,并且可以用(i,j)描述一个数字在金字塔中的位置。
对于中间的一个点来说,想经过它则必须经过它的上方或左上(针对变化后的三角形)。也就是说经过这个点的数字和最大等于经过上方或左上方所得的“最大和”中一个更大的加上这个点中的数字。显然这个定义满足最优子结构。
这样阶段很明显就是金字塔的层,设计一个二维状态opt[i,j]表示走到第i行第j列时经过的数字的最大和。决策就是opt[i-1,j] 或opt[i-1,j-1]中一个更大的加上(i,j)点的数字。
对于一个点只考虑上面或左上即前一阶段,满足无后效性。
状态转移方程(顺推):
opt[i-1,j]+a[i,j] (j=1)
opt[i,j]= opt[i-1,j-1]+ a[i,j] (j=i)
max{opt[i-1,j],opt[i-1,j-1]}+ a[i,j] (1<j<i)
实现时可以将opt[i,j]的左右边界定义的大点,初始opt[i,j]=0
由于在j=1时opt[i-1,j-1]=0,opt[i-1,j]>=0所以方程也可以这样写:
opt[i,j]=max{opt[i-1,j],opt[i-1,j-1]}+a[i,j]
同理j=i时方程也可以写成上面那样,所以方程综合为:
opt[i,j]=max{opt[i-1,j],opt[i-1,j-1]}+a[i,j](0<j<=i)
显然答案是走到底后的一个最大值,即:
ans=max{opt[n,i]} (1<=i<=n)
其实从上往下走和从下往上走结果是一样的,但是如果从下往上走结果就是opt[1,1]省下求最大值了,所以方程进一步改动(逆推):
opt[i,j]=max{opt[i+1,j],opt[i+1,j+1]}+a[i,j](0<j<=i)
复杂度:状态数O(N2)*转移代价O(1)=O(N2)。
C/C++参考代码:
//http://www.structea.cn/post/22.html
//*DP--顺推法
//求最小路径得分
//递归方程
//D(X,1)=D(X-1,1)+a(X,1)
//D(X,y)=min{D(X-1,y),D(X-1,y-1)}+a(X,y)
//D(1,1)=a(1,1),k=1,…,N
//D(N,i),i=1,2,…,N,中最小的为最小路径得分
//PKU1163、HDU2084
#include <iostream>
using namespace std;
int sou[100][100];
int main()
{
int i,j,n,tmp;
cin>>n; //输入数据行数
for (i=0;i<n;i++)
for (j=0;j<=i;j++)
cin>>sou[i][j]; //输入各行数据
for (i=1;i<n;i++)
{
sou[i][0]+=sou[i-1][0];
for (j=1;j<i;j++)
{
tmp=sou[i-1][j-1];
if (sou[i-1][j]<tmp)
tmp=sou[i-1][j];
sou[i][j]+=tmp;
}
sou[i][j]+=sou[i-1][j-1];
}
tmp=sou[n-1][0];
for (i=0;i<n;i++)
if (sou[n-1][i]<tmp)
tmp=sou[n-1][i];
cout<<tmp<<endl;
return 0;
}
//DP--逆推法
//求最小路径得分
//递归方程
//D(X,y)=min{D(X+1,y),D(X+1,y+1)}+a(X,y)
//D(N,k)=a(N,k),k=1,…,N
//D(1,1)即为最小路径得分
/*
#include <iostream>
using namespace std;
int sou[100][100];
int main()
{
int i,j,n,tmp;
cin>>n; //输入数据行数
for (i=0;i<n;i++)
for (j=0;j<=i;j++)
cin>>sou[i][j]; //输入各行数据
for (i=n-2;i>=0;i--)
{
for (j=0;j<=i;j++)
{
tmp=sou[i+1][j];
if (sou[i+1][j+1]>tmp)
tmp=sou[i+1][j+1];
sou[i][j]+=tmp;
}
}
cout<<sou[0][0]<<endl;
return 0;
}*/
/*涂亚运解
#include<iostream>
using namespace std;
#define MAXN 1001
int g[MAXN][MAXN];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int n,i,j;
while(cin>>n)
{
for(i=0;i<n;i++)
for(j=i;j>=0;j--)
scanf("%d",&g[i][j]);
for(i=n-2;i>=0;i--)
for(j=0;j<n-1;j++)
g[i][j]+=max(g[i+1][j],g[i+1][j+1]);
cout<<g[0][0]<<endl;
}
return 0;
}
*/
除非注明,本站文章均为原创,转载请注明: 文章来自世界大学城