[动态规划]数字三角形、最长上升子序列
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
解法:这道题用递归做是解法很显然的:
但是用递归做会超时,原因是每次向下求解路径时,都会重复走以前走过的路经,也就是存在重复计算。深度遍历每条路径,存在大量重复计算,则时间复杂度为2n,对于 n = 100,肯定超时。
这时候就要介绍我们的动态规划算法了。
基本思想:将原问题分解为相似的子问题,在求解 的过程中通过保存子问题的解求出原问题的解(注意:不是简单分而治之)。
只能应用于有最优子结构的问题(即局部最优解能 决定全局最优解,或问题能分解成子问题来求解)。 1+1=2?
经典组合优化问题的基础,如最短路径、背包问题、 项目管理、网络流优化等。
对于上述这道题,一种可能的改进思想是:从下往上计算,对于每 一点,只需要保留从下面来的路径中和最大的 路径的和即可。
这样就避免了子问题的大量重复计算。另外还可以从空间上优化程序:
没必要用二维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后就能形成一个更长的上升子序列。
有了思路程序就水到渠成了:
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; }
相关文章推荐
- C++动态规划之最长公子序列实例
- C++动态规划之背包问题解决方法
- C#使用动态规划解决0-1背包问题实例分析
- 动态规划
- C++ 动态规划
- 动态规划解决背包问题的核心思路
- DP(动态规划) 解游轮费用问题
- 动态规划的用法——01背包问题
- 动态规划的用法——01背包问题
- 《收集苹果》 动态规划入门
- 《DNA比对》蓝桥杯复赛试题
- 《背包问题》 动态规划
- 自顶向下动态规划解决最长公共子序列(LCS)问题
- 01背包问题
- 初学ACM - 半数集(Half Set)问题 NOJ 1010 / FOJ 1207
- 初学ACM - 组合数学基础题目PKU 1833
- 关于爬楼梯的动态规划算法
- 动态规划 --- hdu 1003 **
- POJ ACM 1001
- POJ ACM 1002