您的位置:首页 > 其它

[动态规划]数字三角形、最长上升子序列

2016-07-10 16:49 274 查看
POJ 1163 数字三角形

7

3 8

8 1 0

2 7 4 4

4 5 2 6 5

在上面的数字三角形中寻找一条从顶部到底边的路径, 使得路径上所经过的数字之和最大。路径上的每一步都 只能往左下或右下走。只需要求出这个最大和即可,不 必给出具体路径。

三角形的行数大于1小于等于100

数字为 0 - 99

解法:这道题用递归做是解法很显然的:

递归
设f(i,j) 为三角形上从点(i,j)出发向下走的最长路经, 则
f(i,j) = max(f(i+1,j), f(i+1,j+1))+d(i,j)
要输出的就是f(0,0)即从最上面一点出发的最长路径。


但是用递归做会超时,原因是每次向下求解路径时,都会重复走以前走过的路经,也就是存在重复计算。深度遍历每条路径,存在大量重复计算,则时间复杂度为2n,对于 n = 100,肯定超时。

这时候就要介绍我们的动态规划算法了。

动态规划简介

动态规划是求解包含重叠子问题的最优化方法

基本思想:将原问题分解为相似的子问题,在求解 的过程中通过保存子问题的解求出原问题的解(注意:不是简单分而治之)。

只能应用于有最优子结构的问题(即局部最优解能 决定全局最优解,或问题能分解成子问题来求解)。 1+1=2?

经典组合优化问题的基础,如最短路径、背包问题、 项目管理、网络流优化等。

对于上述这道题,一种可能的改进思想是:从下往上计算,对于每 一点,只需要保留从下面来的路径中和最大的 路径的和即可。

#define MAX_NUM 100
int N;
int D[MAX_NUM + 10][MAX_NUM + 10];
int Sum[MAX_NUM + 10][MAX_NUM + 10];
// 1
void solution1() {
for (int i = 1; i <= N; i++) {
Sum[N][i] = D[N][i];
}
for (int i = N; i > 1; i--) {
for (int j = 1; j < i; j++) {
if (Sum[i][j] > Sum[i][j + 1])
Sum[i - 1][j] = Sum[i][j] + D[i - 1][j];
else
Sum[i - 1][j] = Sum[i][j + 1] + D[i - 1][j];
}
}
cout << endl;
printf("%d\n", Sum[1][1]);
return;
}

int main() {
cin >> N;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= i; j++)
cin >> D[i][j];
}
solution1();
return 0;
}


这样就避免了子问题的大量重复计算。另外还可以从空间上优化程序:

没必要用二维Sum数组存储每一个Sum(r,j),只要从底层一行行向上递推,那么只要一维数组Sum[100]即可,即只要存储一行的Sum值 就可以。

递归到动态规划的转化

原来递归函数有几个参数,就定义一个几维的数组,数组的下标是递归函数参数的取值范围,数组元素的值是递归函数的返回值,这样就可以从边界开始,逐步填充数组,相当于计算递归函数值的逆过程。

动态规划解题步骤

将原问题分解为子问题:把原问题分解为若干个子问题,子问题和原问题形式相同 或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)。子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。

确定状态:在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。

确定一些初始状态(边界状态)的值:以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值。

确定状态转移方程:定义出什么是“状态”,以及在该 “状态”下的“值”后,就要 找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(“人人为我”递推型)。状 态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。数字三角形的状态转移方程:



让我们再看第二个栗子:

例题:最长上升子序列:

一个数的序列bi,当b1 < b2 < … < bS的时候,我 们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N

比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升 子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8)或(1, 3, 4, 8).

找子问题:以ak为终点的序列可以作为子问题。

状态:子问题只与数字—变量的位置有关,即可将位置k看作是状态,k位置对应的即是以ak为终点的序列的最短序列长度。

状态转移方程:maxLen (k)表示以ak做为“终点”的 最长上升子序列的长度那么:

初始状态:maxLen (1) = 1

maxLen (k) = max { maxLen (i):1<=i < k 且 ai < ak且 k≠1 } + 1

若找不到这样的i,则maxLen(k) = 1 maxLen(k)的值,就是在ak左边,“终点”数值小于ak ,且长度最大的那个上升子序列的长度再加1。因为ak左边任何“终点”小于 ak的子序列,加上ak后就能形成一个更长的上升子序列。

有了思路程序就水到渠成了:

#define MAX_NUM 1010
int a[MAX_NUM];
int MaxLen[MAX_NUM];
int main(int argc, char const *argv[]) {
int i, j, N;
cin >> N;
for (i = 1; i <= N; i++) {
cin >> a[i];
MaxLen[i] = 1;
}
for (i = 2; i <= N; i++) //每次求以第i个数为终点的最长上升子序列长度
for (j = 1; j < i; j++) { //求以第j个数为终点的最长上升子序列长度
if (a[j] < a[i])
MaxLen[i] = max(MaxLen[i], MaxLen[j] + 1);
}
cout << *max_element(MaxLen + 1, MaxLen + N + 1);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  动态规划 poj