最长上升子序列(O(n^2)与O(nlogn)+二分)最长公共子序列
最长上升子序列(LIS)
最长上升子序列是最基本的dp问题,以前一直都只写过O(n^2)的解法,现在终于有时间整理一下了。
把poj上的几道最长上升子序列的水题又重新做了一下,主要有1631、2533、3903
方法一:O(n^2)
dp[i]:表示处理到第i个位置,序列的最长上升子序列末尾为i的长度; a[]数组存储原序列
dp[i] = max{dp[j]+1},a[i]>a[j],0≤j≤i
方法二:O(nlogn)
方法一中求dp[i]时需要O(n)的复杂度,其实我们最后只需要知道最大的dp[j]+1就可以了,所以如果能够维护一个单调的数组,从而即可实现二分查找。
dp[]和a[]与方法一中具有同样意义,再添加一个maxv[]数组,maxv[l]表示包含的最长上升子序列长度为l时,末尾的最小的数的值,举个例子:
a[]: 1、2、3、-1、1、2、3、1
dp[]: 1、2、3、1 、2、3、4、2
处理完第N位最后的maxv就会是:-1、1、2、3,毋庸置疑,maxv肯定是递增的,而且找到最小的值就保证了对于固定长度l时,可以直接从存储在maxv中的数得到最优解(呃,不知道怎么说清楚了),maxv在求解dp的过程中也是要不断更新的。
如果最后要输出解的情况的话,那么maxv中就不能简单的存储固定长度对应的原序列的最小值了,而要记录下编号,好以后利用pre数组递归回去,而此时满足单调的数组就不是maxv[i]与i的关系了,而是a[maxv[i]]与i的关系了,更新maxv数组时也要注意。
不输出解(poj 3903):
输出解(没有找到题号,自己写的):
最长公共子序列(LCS)
最长公共子序列的基本转移方程为:d[i][j] = max{d[i'][j']}+1, p[i] = q[j],i'>i,j'>j
基本的典型题是POJ 1159,附代码
1 /*也可进一步用滚动数组优化*/ 2 3 #include<iostream> 4 #include<cstdio> 5 #include<cmath> 6 #include<algorithm> 7 #include<cstring> 8 using namespace std; 9 short dp[5010][5010]; 10 11 char s1[5010], s2[5010]; 12 int main(){ 13 int n, m, i, j, k; 14 scanf("%d",&n); 15 cin>>s1; 16 for(i=0;i<n;i++){ 17 s2[i] = s1[n-1-i]; 18 } 19 memset(dp,0,sizeof(dp)); 20 21 for(i=1;i<=n;i++){ 22 for(j=1;j<=n;j++){ 23 if(s1[i-1]==s2[j-1]){ 24 dp[i][j] = dp[i-1][j-1] + 1; 25 } 26 else{ 27 dp[i][j] = max(dp[i][j-1],dp[i-1][j]); 28 } 29 } 30 } 31 printf("%d\n",n-dp[n][n]); 32 return 0; 33 }
据说,LIS也又O(nlogn)的做法,使用静态二叉树(黑书上又说),目前还不会
另外,最长上升子序列(LCS)和最长公共子序列(LIS)其实可以互相转换。
LCS -> LIS:把序列排序后与原序列找最长公共子序列
LIS -> LCS:设有序列A,B。记序列A中各个元素在B中的位子(降序排列),然后按在A中的位置依次列出然后求A的最长递增子序列。
例如:
A串位置 1 2 3 4 B串位置: 1 2 3 4
A串: 4 3 5 2 B串: 2 5 4 3
A串在B串--位置 3 4 2 1 因为B串已经顺序了,只要求出这行的最长递增子序列 就是最长公共子序列的长度了。
例如:有A={a,b,a,c,x},B={b,a,a,b,c,a}则有a={6,3,2},b={4,1},c={5};x=/;(注意降序排列)
然后按A中次序排出{a(6,3,2),b(4,1),a(6,3,2),c(5),x()}={6,3,2,4,1,6,3,2,5};对此序列求最长递增子序列即可
不过这种转换好像没有太大的帮助