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

Written with StackEdit.

二维动态规划

引入Ⅰ

一道很简单的奥数题:
一个 m × n m\times n m×n的方格, 从左上角到右下角的路线有多少种?
很容易想到上边和左边的所有节点的种数只有1,而对于其他的点有:
F ( x , y ) = F ( x − 1 , y ) + F ( x , y − 1 ) F(x,y)=F(x-1,y)+F(x,y-1) F(x,y)=F(x1,y)+F(x,y1)
所以从[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) F(i,j)=max{F(i1,j1),F(i,j1)}(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\} X={x1,x2,,xm} Y = { y 1 , y 2 , … , y n } Y=\{y_1,y_2,…, y_n\} Y={y1,y2,,yn},找出X和Y的一个最长公共子序列。

穷举

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

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

分析

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

最优子结构

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

  1. X m = Y n = Z k X_m=Y_n=Z_k Xm=Yn=Zk, C ( m − 1 , n − 1 ) = k − 1 C(m-1,n-1)=k-1 C(m1,n1)=k1
  2. X m ≠ Y n   a n d   Z k ≠ X m X_m\not =Y_n\ and\ Z_k\not =X_m Xm=Yn and Zk=Xm, C ( m − 1 , n ) = k C(m-1,n)=k C(m1,n)=k
  3. Z k ≠ T Y n Z_k\not =TY_n Zk=TYn亦同.
状态转移

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 C(i,j)=0,when ij=0C(i1,j1)+1,when Xi=YjMAX{C(i1,j),C(i,j1)},when Xi=Yj

代码示例

'''
@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
下回试试非递归?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值