关于LIS和LCS问题的o(nlogn)解法
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; }