其实关键在于模型的转化,对于两个数列a,b,我们可以用一个O(n^2)的枚举求出其最长公共子序列,但在一些题中就显然不满足了,所以有了nlog的算法。
首先我们要找到a序列中每个元素在b序列中的位置,这个位置可能有多个,我们将它从大到小排列,对于b中不存在的元素这个位置显然是空集,这样我们就得到了a中每个元素在b中的位置也就是多个集合,且集合内部单调递减,然后按照a中元素的先后顺序,将得到的这多个集合合并在一起,我们可以得到一个新的数列,在这个数列中求最长上升子序列就可以。
算法的正确性很显然,对于新得到的数列,每一个元素都代表了a与b有共同元素,而上升则保证了序列的顺序从前到后。
这里有关键一点,在找a序列在b中位置存储时,要从大到小,这样就放止了对于a中一个元素匹配到b中多个元素的可能性。
洛谷模板题:https://www.luogu.org/problem/P1439
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <vector> 6 using namespace std; 7 #define N 100005 8 int read() { 9 int s=0,f=1; 10 char ch=getchar(); 11 for( ; ch<'0'||ch>'9'; f=(ch=='-')?-1:f,ch=getchar()) ; 12 for( ; ch>='0'&&ch<='9'; s=s*10+(ch^48),ch=getchar()) ; 13 return s*f; 14 } 15 vector<int> q[N]; 16 int n,a[N],b[N],Low[N],ans=1,Now[N]; 17 int find(int Now,int RR) { 18 int L=0,R=RR,mid; 19 while(L<R) { 20 mid=(L+R)>>1; 21 if(Low[mid]>Now) R=mid; 22 else L=mid+1; 23 } return L; 24 } 25 int main() { 26 n=read(); 27 for(int i=1; i<=n; ++i) a[i]=read(); 28 for(int i=1; i<=n; ++i) b[i]=read(); 29 for(int i=n; i; --i) q[b[i]].push_back(i); 30 for(int i=1; i<=n; ++i) { 31 if(q[a[i]].empty()) continue; 32 for(int j=0; j<q[a[i]].size(); ++j) Now[++Now[0]]=q[a[i]][j]; 33 } Low[ans]=Now[1]; 34 for(int i=2; i<=Now[0]; ++i) { 35 if(Now[i]>Low[ans]) Low[++ans]=Now[i]; 36 else Low[find(Now[i],ans)]=Now[i]; 37 } cout<<ans; 38 return 0; 39 }