给出 1∼n 的两个排列 P1 和 P2,求它们的最长公共子序列。
输入格式第一行是一个数 n (1≤n≤105)。
接下来两行,每行为 n 个数,为自然数 1∼n 的一个排列。
输出格式一个数,即最长公共子序列的长度。
Sample Input
5 3 2 1 4 5 1 2 3 4 5
Sample Output
3思路 1.dp做法 O(n²)
首先要知道,对于单独的一个元素来说,其最长上升子序列就是它本身,所以dp的每一个元素一开始都应该是1
然后dp[i]可用于记录在 i 之前的每一个元素的最优解,如果当前的dp[i]大于之前的最优解,即可继承下去
for(int i = 1; i <= n; ++i)
{
dp[i] = 1;//对dp数组进行初始化
for(int j = 1; j < i; ++j)
{
if(a[j] < a[i] && dp[i] < dp[j] + 1)//先判断该是否为上升子序列,再判断子序列是否更长
{
dp[i] = dp[j] + 1;//如果是,则继承结果并加一
}
}
}
在本题的条件中,10^5显然会超时,所以此方法在本题不适用。
2.二分查找做法 O(nlogn)仔细观察题目发现,两个序列都是从1到n的全排列,所以两个序列的元素都是相同的,只是位置不一样而已,这时候我们再用一个数组t将序列P1中的元素在序列P2中的位置表示出来。为什么呢?
假设有以上两个序列,为了更加直观我用小写字母按从小到大的顺序来表示P1中各元素
这个时候 a—1 b—3 c—4 d—2 e—5
再用这些字母去替代P2
这时候P2序列就变成了edbac,不难发现P2中的最长升序子序列为 bc 或 ac ,更巧的是其代表的元素 34 和 14 正好也是P1中的子序列,当然这不是个巧合,同学们深思一下就能想明白其中的道理了
这时候题目就变得简单了,我们只需再用一个数组存P1对应元素的顺序
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
t[a[i]] = i;//将各元素所在位置存起来
}
再用这个顺序代替P2,最后在变化后的P2中找出其最长上升子序列就可以了,用二分随便搞搞就做完了。其中找最长上升子序列的方法在这里有细讲。
代码#include#include using namespace std; int n, p1[100005], p2[100005], t[100005], f[100005], len = 0; //数组t用于存放p1中元素的位置,t[2] = 1就表示元素2在p1中出现的位置时第1位 //数组f则是用来存放替换后的数组p2 int main() { cin >> n; for (int i = 1; i <= n; ++i) { cin >> p1[i]; t[p1[i]] = i; } for (int i = 1; i <= n; ++i) { cin >> p2[i]; } for (int i = 1; i <= n; ++i) { if (t[p2[i]] > f[len])//如果该元素大于上一个,说明可以接上去,然后再令最大值等于该元素 { f[++len] = t[p2[i]]; } else//如果不是,则找数组f中第一个大于等于该元素的元素位置,并将其接在后面 { f[lower_bound(f + 1, f + len + 1, t[p2[i]]) - f] = t[p2[i]]; } } cout << len;//最后输出最长的len return 0; }



