其实关键在于模型的转化,对于两个数列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 }
