题目描述
给出1-n的两个排列P1和P2,求它们的最长公共子序列。
输入输出格式
输入格式:第一行是一个数n,
接下来两行,每行为n个数,为自然数1-n的一个排列。
输出格式:一个数,即最长公共子序列的长度
输入输出样例
输入样例#1:
5 3 2 1 4 5 1 2 3 4 5
输出样例#1:
3
说明
【数据规模】
对于50%的数据,n≤1000
对于100%的数据,n≤100000
分析:经典的LCS做法只有50分,100分的做法很显然是O(nlogn)的,将n改为logn能想到的就是二分,但是在LCS问题中要求的是最长公共的,并不满足二分的性质,只有是单调的才能用二分,考虑转化为LIS来做,如果两个序列都是单调上升的,那么答案就是第二个序列的最长上升子序列,关键是题目给的并不是单调上升的,怎么解决呢?我们把第一个序列编号,然后第二个序列按照第一个序列的编号来改动一下,这样第一个序列就是单调上升的了,我们只需要找到第二个序列的最长上升子序列就好了.
知道要二分,但是要二分什么东西呢?有一种神奇的做法,维护一个栈,考虑第i个元素j,如果j大于栈顶元素,则直接放到栈顶,否则二分找到第一个大于j的数,用j把他替换掉,答案就是栈的大小.原理是什么呢?因为我们是从左往右扫的,根据贪心思想,最后一个数越小越好,替换操作并不是真的替换,而是表示以后的数可以插入在这个位置,毕竟我们只要栈大小,不要求具体的数.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <map> using namespace std; int n,s1[100010],s2[100010],st[100010],top,mm[100010]; int erfen(int x) { int l = 1,r = top; while (l < r) { int mid = (l + r) >> 1; if (st[mid] <= x) l = mid + 1; else r = mid; } return r; } int main() { scanf("%d",&n); for (int i = 1; i <= n; i++) scanf("%d",&s1[i]); for (int i = 1; i <= n; i++) scanf("%d",&s2[i]); for (int i = 1; i <= n; i++) mm[s1[i]] = i; for (int i = 1; i <= n; i++) s2[i] = mm[s2[i]]; for (int i = 1; i <= n; i++) { if (s2[i] > st[top]) st[++top] = s2[i]; else st[erfen(s2[i])] = s2[i]; } printf("%d ",top); return 0; }