ACM总结——最长公共子序列 & 最长不减(不增)子序列

来源:互联网 发布:如何靠网络挣钱 编辑:程序博客网 时间:2024/04/29 14:28

转自:http://blog.sina.com.cn/s/blog_6a3344990100xyhg.html

ACM总结——最长公共子序列 & 最长不减(不增)子序列
两个经典DP
一、最长公共子序列(Longest Common Subsequence:LCS)

设有两个序列A[1...m]和B[1...n],分别对A和B进行划分子序列
A[1] A[1..2] A[1..3] ... A[1..m]
B[1] B[1..2] B[1..3] ... B[1..n]
依次求出A中的每个子序列(从A[1]开始)与B中每个子序列的最长公共子序列,并记录在数组C[m][n]中,C[i][j]表示A[1..i]和B[1..j]的最长公共子序列的长度。

递推公式如下:
①C[i][j]=0 i=0 or j=0
②C[i][j]=C[i-1][j-1]+1 i!=0 and j!=0 and A[i]=B[j]
③C[i][j]=max{C[i-1][j],C[i][j-1]} i!=0 and j!=0 and A[i] != B[j]

路径记录:
记录路径即记录C[i][j]是怎么得来的,从递推公式②③知,C[i][j]的来源有三个:C[i-1][j-1],C[i-1][j],C[i][j-1]。如果是从C[i-1][j-1]得来,那么A[i]=B[j],是最长公共子序列中的一个元素。
可以设置一个数组P[m][n]来记录当前的C[i][j]是怎么得来的,P[m][n]的取值只能有三种,分别记为1 2 3。

构造最长公共子序列:
用递归的方法,检查P[i][j],初始i=m,j=n
如果p[i][j]=1,则记录C[i][j],然后递归处理P[i-1][j-1]
如果P[i][j]=2,不记录,递归处理P[i-1][j]
如果P[i][j]=3,不记录,递推处理P[i][j-1]
直到i=0 or j=0

时间复杂度:O(mn)


二、最长不减(不增)子序列(Longest Order Subsequence)

序列:A[1..n]

方法1:
可以转化为最长公共子序列问题,把序列排序后与原序列找最长公共子序列即可。排序O(nlogn),求最长公共子序列O(n^2),所以时间复杂度为O(n^2)

方法2:
类似上面的方法划分子序列
A[1] A[1..2] A[1..3] ... A[1..n]
依次对每一个序列求最长不减子序列,求后面的会用到前面的结果。用C[1..n]记录求的的长度,即C[i]为A[1..i]最长不减子序列的长度

递推公式:
①C[i]=1 i=1;
②C[i]=max{C[j] | j<i and A[j]<=A[i]}+1 i>1

时间复杂度:
求C[i],需要遍历C[1]...C[i-1],找出最大值(对应的A[j]必须小于等于A[i]),所以时间复杂度为O(n^2)

方法3:
对方法2进行优化,方法2中求C[i]时需要遍历C[1]...C[i-1],这个遍历可以进行优化。
引入一个数组g[n],g[i]表示子序列长度为i时,序列最后一个元素的最小值,初始时,g[n]赋值无穷大,很容易看出,随着子序列长度的增加(即g的下标的增加),序列最后一个元素值必定不会减小(即数组g中的值),所以数组g[n]是单调不减的。
C[i]即等于g[n]中的第一个大于A[i]的元素的下标index,然后用A[i]替换数组g[n]中的这个元素,来更新数组g[n],因为A[i]是序列长度为index的的序列中更小的末元素。
因为g[n]是有序的,所以可以用二分搜索来查找。

这个二分写的很郁闷,怪不得有句话叫做90%程序员写不出无BUG的二分查找程序,这个二分要找出第一个大于某个值的下标,应该把各种情况的二分好好整理一下。

时间复杂度:O(nlogn)

相关题目:

POJ1458
POJ2250
POJ1159
基本的最长公共子序列

POJ2533
最长不减(不增)子序列,用方法1、2即可

WOJ1398

最长不减(不增)子序列,方法1、2超时


题解:http://blog.csdn.net/sinat_19628145/article/details/51161028

0 0
原创粉丝点击