最长公共上升子序列的另一个O(mn)的算法
icon2 Program Impossible | icon4 2006-10-18 15:36| icon39 Comments | 本文内容遵从CC版权协议 转载请注明出自matrix67.com

    我在这个帖子里说过nlogn求最长上升子序列的方法:
    http://www.oibh.org/bbs/viewthread.php?tid=10682
    下面引用我自己的发言:


     f表示长度为i的上升子序列最后一个数最小是多少。显然数组f是单增的。
     读到一个新的数x后,找到某个i使得x>f[i]且x<=f[i+1],于是用x去更新f[i+1];特别地,如果所有的f[i]都小于x,则增加f的长度。
     最后看f数组有多长就行了。
     由于f单增,所以查找i时可以用二分查找,因此时间复杂度为O(nlogn)。
     举个例子,假如序列为 3 2 8 6 7 4 5 7 3,则f数组的变化过程如下:
     3
     2
     2 8
     2 6
     2 6 7
     2 4 7
     2 4 5
     2 4 5 7
     2 3 5 7
     最后,f的长度达到4,因此答案为4。
     注意,最后的f数组不一定是最长上升子序列的一个方案。



    这里要说的这个算法利用了nlogn的最长上升子序列(LIS)的技巧:用f[k]表示长度为k的上升子序列最后一个数最小是多少。
    在最长公共上升子序列中,令f[i,j][k]表示A串前i个数字,B串前j个数字,长度为k的公共上升子序列中,最后一个数最小是多少。

    当A[i]=B[j]时,像nlogn的最长上升子序列一样把A[i]插入到f[i-1,j]中,这需要线性的时间扫一遍f[i,j];
    当A[i]<>B[j]时,我们需要合并f[i-1,j]和f[i,j-1],使得对于每个k满足f[i,j][k]:=min{ f[i-1,j][k],f[i,j-1][k] }。这需要线性的时间扫一边f[i-1,j]和f[i,j-1]并取k相同时的较小值。
    最后输出f[n,m]的长度(使f[n,m][k]有意义的最大的k)。
    这样的复杂度是三方的,我们需要优化。

    考虑A[i]=B[j]的情况。当i固定时,随着j的增加,插入的位置一定也在后移,因为同样是插入的A[i],但j的增加(B串长度的增加)使得f [i,j]更优,因此可以更新的值就更靠后。于是,对于每个i,我们可以按照k的顺序扫描f[i-1,j][k] 并在A[i]可以插入f[i-1][j]的k位置时增加j,从而预处理所有A[i]=B[j]时A[i]应该插入的位置。
    再考虑A[i]<>B[j]的情况。从定义看,f[i-1,j-1]和f[i-1,j]只有一个地方不一样,因为多一个数最多只能造成一个k 的值变小;同样地,f[i-1,j-1]和f[i,j-1]也只有一个地方不一样。因此,f[i-1,j]和f[i,j-1]最多只有两个k所对应的值不相同,且当有两个不同的值时,总是f[i-1,j]中的某个值较小,f[i,j-1]中的某个值较小。这给我们优化的余地。在每次处理完f[i,j]时,我们可以记录一个值x[i,j]表示f[i,j][k]与f[i-1,j][k]中值不一样的k是多少,在A[i]=B[j]时直接赋值为插入的位置,在 A[i]<>B[j]时待后文说明。以后合并时,先让f[i,j]:=f[i-1,j](由于此时的f[i-1,j]已经没有别的用处了,因此可以用滚动数组记录,直接令f[i-1,j]是f[i,j],避免实际的赋值操作),然后将新的f[i,j]中的,使f[i,j-1][k]比f[i- 1, j][k]小的k所对应值更新。这个k是多少呢?显然应该是x[i,j-1]。这样的操作同时可以确定x[i,j]=x[i,j-1]。
    这样,复杂度就达到了平方。

    附参考的资料(原来从这篇论文里学到的,不知道有没有此类的中文资料,估计没有才在这里写了一个,感兴趣的话可以下载附件仔细研究)

点击下载此文件

Matrix67原创
转载请注明出处

9 条回复

  • 楼层: 沙发 | | fran 说:

    看不懂O(mn)的解释~~能发标程不??

    回复:看一看我提供的原文pdf吧,里面有代码

  • 楼层: 板凳 | | J 说:

    精妙~很好 很强大

  • 楼层: 地毯 | | 晗熙 说:

    问个有关最长不下降序列的问题???
    词链(chain.???)
    问题描述:
    一个词是由至少1个、至多75个小写英文字母组成的。当在一张1个或多个词组成的表中,每一个词(除第一个)都能由在其前一个词的词尾添加1个或多个字母而得到的话,则称此表为一个词链。
    例如下面的表:
    i
    in
    int
    integer
    为一个含四个词的链,而表:
    input
    integer
    不是链。
    一个链的长度是指该链所含词的个数。含一个词的表也是链,其长度为1。

    输入格式:
    你将从输入文件中读到一张表,文件以“.”结束。每行含一个词,表中至少有1个词,而所有词所含字母个数之总和不超过2 000 000个。文件中各行已按字典顺序由小到大排序,且文件中的词不会有重复。

    输出格式:
    你的程序应从输入文件中找出最长的链,并把该链写入到输出文件中。
    要求一个词占一行。
    若有多个最长链,只须输出其中任何一个。

    输入输出举例:
    下面给出了一个由7个词的输入文件和一个对应的输出文件。输入文件中有一个长度为4的链,这里已没有比它更长的链了。
    输入chain.in    
    i
    if
    in
    input
    int
    integer
    output
    .
    输出chain.out
    i
    in
    int
    integer

  • 楼层: 地板 | | spidermandf 说:

    关于您在论坛发言的那个方法有没标程的.......

  • 楼层: 地下室 | | tomtao26 说:

    最长不下降子序列=最长下降子序列的个数
    有点贪心的味道

  • 楼层: 地基 | | Broken 说:

    你的附件下不了,能更新一下么?

    BTW,你这些资料哪来的?我怎么就搜不到呢。

  • 楼层: 地壳 | | Reikon 说:

    最长公共上升子序列....没有“公共”吧..

  • 楼层: 地幔 | | Reikon 说:

    我说错了。。。

  • 楼层: 地核 | | Reikon 说:

    那个pdf文件不能下载哦

您也随便说几句吧:

请注意:如果您是第一次在本站发表评论,您的评论需要通过管理员的审核。

您可以在Gravatar设置您的头像。