P1439 【模板】最长公共子序列 题解
简要题意:
给定两个 \(1\) ~ \(n\) 的排列,求其 最长公共子序列。
嗯,下面给出若干算法吧。
算法一
不管它是 \(1\) ~ \(n\) 的排列这一性质。
求 \(\text{LCS}\)(即最长公共子序列)的套路方法:
用 \(f_{i,j}\) 表示 \(a_1\) ~ \(a_i\) 和 \(b_1\) ~ \(b_j\) 的最长公共子序列。那么不考虑边界问题,则存在:
\[f_{i,j} = \begin{cases} f_{i-1,j-1}+ 1 , a_i = b_j \\
\max(f_{i,j-1} , f_{i-1,j}) \end{cases}\]
显然,当前位相等则一起缩,否则一个缩。
时间复杂度:\(O(n^2)\).
实际得分:\(50pts\).
算法二
嗯,你去百度上搜了一下 \(\text{LCS}\) 的求法,然后发现最优的 \(\text{dp}\) 就是 \(O(n^2)\)?
但是注意到一个性质 ,两个数组都是 \(1\) ~ \(n\) 的排列。
所以今天我们要秀出什么操作呢!
5
3 2 1 4 5
1 2 3 4 5
嗯,如果我们把 3 2 1 4 5
变成 1 2 3 4 5
,即建立一种关系为:
3 - 1
2 - 2
1 - 3
4 - 4
5 - 5
然后 1 2 3 4 5
它就变成了 3 2 1 4 5
(这纯属巧合啊)。
你发现,通过这样一波操作,其实 \(\text{LCS}\) 本质就是 在 \(b_i\) 中找到若干个在 \(a_i\) 中有序出现的子序列。
显然一波操作之后 \(a_i\) 有序,然后就是直接求 \(b_i\) 的 \(\text{LIS}\)(最长上升子序列)即可。因为任意的两个 \(x < y\) 显然对应的 \(a_i\) 也满足有该序列。
时间复杂度:\(O(n \log n)\).
实际得分:\(100pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int a2[N],a1[N],be[N];
int b[N],f[N],ans,n;
int main(){
n=read(); for(int i=1;i<=n;i++)
a1[i]=read(),be[a1[i]]=i; //建立对应关系
for(int i=1;i<=n;i++) a2[i]=read();
for(int i=1;i<=n;i++) {
if(be[a2[i]]>b[ans]) { //正常套路 LIS
b[++ans]=be[a2[i]]; f[i]=ans; continue;
} int t=lower_bound(b+1,b+1+ans,be[a2[i]])-b;
b[t]=be[a2[i]]; f[i]=t;
} printf("%d\n",ans);
return 0;
}
简易的代码胜过复杂的说教。