最長上升子序列(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):
View Code
輸出解(沒有找到題號,自己寫的):
View Code
最長公共子序列(LCS)
最長公共子序列的基本轉移方程為:d[i][j] = max{d[i'][j']}+1, p[i] = q[j],i'>i,j'>j
基本的典型題是POJ 1159,附代碼
View Code
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};對此序列求最長遞增子序列即可
不過這種轉換好像沒有太大的幫助
