呃……首先说明一下,这是第一次做DP,所以如果题解说的有什么问题还帮忙指正一下。
首先附上原题:
ProblemDescription
在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
已经告诉你了,这是个DP的题目,你能AC吗?
Input
输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。
Output
对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
SampleInput
1
5
7
3 8
8 10
2 74 4
4 52 6 5
SampleOutput
30
据说这题是DP的入门级题目,虽然瞄了一眼别人的题解,然而并没有什(zhi)么(shang)灵(zhuo)感(ji)。于是自己想啊想想出了这方法。
先看问题的过程是什么。一次向上走一层。
问题的最后一个步骤,情况只有两种:左儿子和右儿子。
然后是问题分析:第一,每个子问题都存在一个唯一最优解,这是最优子结构;第二,除了最后一层每一层的每一个问题都是相同类型的问题,即在自己的两个子节点中选择最大的那个,这是子问题重叠;第三,当问题到了最后一层子节点时,最优解即为该子节点本身(唯一),这是边界;第四,对于每一个节点,父节点只需要找出两个儿子节点中较大的而不需要考虑子节点的求解过程,这是子问题独立。
问题分析确定四要素存在,决定了本题是DP适用问题。
然后考虑如何做备忘录。每次向上走一层,即每一层的节点都找到最优解后再向上走一层,而上面的那一层判断依据只有下面一层的最优解,也就是说每次存储的内容只需要最上面一层的最优解即可。
然后再看时间。本题的子问题个数最多只有n个(其中已有个有最优解了),所以时间复杂度理论上不会超过O(n)。
最后出状态转移方程。对于每一个子节点(从倒数第二层开始),每一个都对应一个子节点,将两个儿子中较大的那个加入子节点作为本节点最优解,于是得出方程
inum[i][j] += (inum[i+1][j+1]>inum[i+1][j]) ? inum[i+1][j+1] :inum[i+1][j];
其中inum是存储数据用的二维数组,原始数据存储于此;i和j代表父节点的层数和位置,i+1行j个和j+1个分别是左儿子和右儿子。
以下是代码:
#include<cstdio>
using namespace std;
int im,in,inum[100][100];
int main(){
scanf("%d",&im);
for(int k=0; k<im; k++){
scanf("%d",&in);
for(int i=0; i<in; i++){
for(int j=0; j<=i; j++){
scanf("%d",&inum[i][j]);
}
}
for(int i=in-2; i>-1; i--){
for(int j=i; j>-1; j--){
inum[i][j] +=(inum[i+1][j+1]>inum[i+1][j]) ? inum[i+1][j+1] : inum[i+1][j];
}
}
printf("%d\n", inum[0][0]);
}
return 0;
}