o(n^2)解法就不贅述了,直接解釋o(nlogn)解法
LIS最長遞增子序列;
先明確一個結論:在長度最大為len的遞增序列里若末尾元素越小,該遞增序列越容易和后面的子序列構造出一個更長的遞增子序列。也即認為,長度為len的遞增子序列中末尾元素最小的那種最需要保留。我們不妨稱這個目前找到序列為到目前為止的 最優序列。
因此設置一個數組lis[i]其中 i 表示此時最大遞增序列的長度,數組值表示此時達到 i 的最優序列(也即 長度為len的遞增子序列中末尾元素最小的那種)的末尾元素。
那么此時只需遍歷一遍輸入數據,維護lis的上述特性,則最后所得的lis數組的長度就是要求的len。
不多言,結合代碼理解:
#include<iostream> #include<cstdio> using namespace std; const int maxn=1e5+5; int a[maxn]; int n; int lis[maxn]; int len=1; int find(int x){ int l=1,r=len,m; while(l<r){ m=l+(r-l)/2; if(lis[m]>=a[x]){//這里若去掉等號即為 非嚴格遞增序列 r=m; } else{ l=m+1; } } return l; } int main(void){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]); lis[1]=a[1]; for(int i=2;i<=n;i++){ if(a[i]>lis[len]){ lis[++len]=a[i]; } else{ int pos=find(i); lis[pos]=a[i]; } } printf("%d",len); return 0; }
LCS最長公共子序列:
最長公共子序列 的 nlogn 的算法本質是 將該問題轉化成 最長增序列(LIS),因為 LIS 可以用nlogn實現,所以求LCS的時間復雜度降低為 nlogn。
假設有兩個序列 s1[ 1~6 ] = { a, b, c , a, d, c }, s2[ 1~7 ] = { c, a, b, e, d, a, b }。
記錄s1中每個元素在s2中出現的位置, 再將位置按降序排列, 則上面的例子可表示為:
loc( a)= { 6, 2 }, loc( b ) = { 7, 3 }, loc( c ) = { 1 }, loc( d ) = { 5 }。
將s1中每個元素的位置按s1中元素的順序排列成一個序列s3 = { 6, 2, 7, 3, 1, 6, 2, 5, 1 }。
在對s3求LIS得到的值即為求LCS的答案。(這點我也只是大致理解,讀者可以自己理解甚至證明。)
這里給出全排列情況下的代碼(即兩個序列長度相同,數字組成相同,無重復元素)
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; const int maxn=1e6+5; int n,len=0; int lis[maxn]; int a[maxn]; int b[maxn]; int loc[maxn]; int find(int x){ int l=1,r=len,m; while(l<r){ m=l+(r-l)/2; //if(lis[m]>=b[x]){//智障錯誤,找了那么久。。 if(lis[m]>=x){ r=m; } else l=m+1; } return l; } int main(void){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=1;i<=n;i++){ scanf("%d",&b[i]); loc[b[i]]=i; } for(int i=1;i<=n;i++){ b[i]=loc[a[i]]; } // for(int i=1;i<=n;i++)printf("%d",b[i]) ;// // printf("\n"); if(n!=0)lis[++len]=b[1]; for(int i=2;i<=n;i++){ if(b[i]>lis[len]){ lis[++len]=b[i]; } else{ int pos=find(b[i]); lis[pos]=b[i]; } } printf("%d",len); return 0; }