二维动态规划/最长公共子序列

Written with StackEdit.

二维动态规划

引入Ⅰ

一道很简单的奥数题:
一个 m × n m\times n 的方格, 从左上角到右下角的路线有多少种?
很容易想到上边和左边的所有节点的种数只有1,而对于其他的点有:
F ( x , y ) = F ( x 1 , y ) + F ( x , y 1 ) F(x,y)=F(x-1,y)+F(x,y-1)
所以从[0][0]推导至目标点即可.
而用计算机自动化求解,就用到了二维的DP知识.
当然还会有更复杂的情况,比如路径堵塞等.

引入Ⅱ

设有一个三角形的数塔,顶点结点称为根结点,每个结点有一个整数数值。从顶点出发,可以向左走,也可以向右走.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NeyasLAN-1572451763355)(https://s2.ax1x.com/2019/10/30/K5M8eA.png)]
问题:当三角形数塔给出之后,找出一条从第一层到达底层的路径,使路径的值最大。若这样的路径存在多条,任意给出一条即可.

将树旋转为下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ArfYdV4T-1572451763356)(https://s2.ax1x.com/2019/10/30/K5Mhy4.png)]
F(0,0)=13
状态转移方程为
F ( i , j ) = m a x { F ( i 1 , j 1 ) , F ( i , j 1 ) } ( i f   e x i x t s ) F(i,j)=max\{F(i-1,j-1),F(i,j-1)\}(if\ exixts)
也就是相连的两个节点.

Basic

二维DP维护一个二维数组,其决定函数包含两个元,状态转移方程要考虑两个已经求值的节点.

应用:最长公共子序列

和最长不降/升子序列不同,最长公共子序列需要考虑两个序列,使用的是二维数组进行维护

  • 一个给定序列的子序列是在该序列中删去若干元素后得到的序列。
  • 给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
  • 最长公共子序列:公共子序列中长度最长的子序列。

给定两个序列 X = { x 1 , x 2 , , x m } X=\{x_1,x_2,…,x_m\} Y = { y 1 , y 2 , , y n } Y=\{y_1,y_2,…, y_n\} ,找出X和Y的一个最长公共子序列。

穷举

穷举每一个X的子序列,验证是否是Y的子序列.

  • X有2m个子序列
  • 每个子序列需要o(n)的时间来验证它是否是Y的子序列.
    • 从Yn的第一个字母开始扫描下去,如果不是则从第二个开始
      运行时间: o(n2m)

分析

X i X_i 为X前i个成员的序列.
C ( i , j ) C(i,j) 为序列Xi和Yj的最大公共子序列的长度.

最优子结构

设X和Y最长公共子序列为 Z k Z_k

  1. X m = Y n = Z k X_m=Y_n=Z_k , C ( m 1 , n 1 ) = k 1 C(m-1,n-1)=k-1
  2. X m Y n   a n d   Z k X m X_m\not =Y_n\ and\ Z_k\not =X_m , C ( m 1 , n ) = k C(m-1,n)=k
  3. Z k T Y n Z_k\not =TY_n 亦同.

状态转移

C ( i , j ) = 0 , w h e n   i j = 0 C ( i 1 , j 1 ) + 1 , w h e n   X i = Y j M A X { C ( i 1 , j ) , C ( i , j 1 ) } , w h e n   X i Y j C(i,j)=\\ 0,when\ ij=0\\C(i-1,j-1)+1,when\ X_i =Y_j\\MAX\{C(i-1,j),C(i,j-1)\},when \ X_i \not=Y_j

代码示例

'''
@Description: 使用递归的最长公共子序列
@Date: 2019-10-30 23:35:31
@Author: I-Hsien
@LastEditors: I-Hsien
@LastEditTime: 2019-10-30 23:49:14
'''
def build(arr1,arr2,i:int,j:int):
    if (i*j==0):
        return 0
    if (arr1[i-1]==arr2[j-1]):
        return build(arr1,arr2,i-1,j-1)+1
    if (arr1[i-1]!=arr2[j-1]):
        return max(build(arr1,arr2,i-1,j),build(arr1,arr2,i,j-1))
if __name__=="__main__":
    array=input().split()
    brray=input().split()
    print(build(array,brray,len(array),len(brray)))

改进:剪裁了每次递归传入的序列,减少递归内存开销.

'''
@Description: 改进版本;剪裁了每次递归传入的序列.
@Date: 2019-10-30 23:52:10
@Author: I-Hsien
@LastEditors: I-Hsien
@LastEditTime: 2019-10-30 23:59:52
'''
def build(arr1,arr2):
    if arr1 ==[] or arr2 ==[]:
        return 0
    if (arr1[-1]==arr2[-1]):
        return build(arr1[:-1],arr2[:-1])+1
    if (arr1[-1]!=arr2[-1]):
        return max(build(arr1[:-1],arr2),build(arr1,arr2[:-1]))
if __name__=="__main__":
    array=input().split()
    brray=input().split()
    print(build(array,brray))

放OJ上跑还是爆栈了…XD
下回试试非递归?

发布了80 篇原创文章 · 获赞 13 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/POTASSIUM711/article/details/102829765