代码源:740、最长公共子序列(nlogn)

323 阅读3分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路 logo.png

题目描述

这是4月15日代码源div2的每日一题。

最长公共子序列 - 题目 - Daimayuan Online Judge

给出从 1 到 n 的两个排列 P1 和 P2,求它们的最长公共子序列。

输入格式

第一行是一个正整数 n。

接下来两行,每行为 n 个数,为自然数 1,2,…,n 的一个排列。

输出格式

一个数,即最长公共子序列的长度。

数据范围

1≤n≤10^5

输入样例

5 
3 2 1 4 5
2 1 3 4 5

输出样例

4

\

问题解析

第一眼看这题以为是个模板题,但一看这数据范围就知道这题不简单。

大火可能都写过二维dp求最长公共子序列,它的时间复杂度是n^2。但在这里数据被提到了1e5,先不说超不超时,我们也开不了1e5* 1e5的二维数组。那么这题该怎么做?有没有什么方法可以不用二维数组,还能把复杂度降到nlogn(或者更低?)。

首先,有两点,一是这里给的两个数组,都是自然数1、2、3……n的排列,只是两个数组中的位置不一样罢了。二是我们要考虑一下公共子序列的意思,公共序列换个说法就是,一个序列在两个数组中的相对位置都是一样的。比如样例这里,2都是在1后面,4在2后面,5在2后面。所以它们的最长公共序列就是2145,长度为4。然后更直观点来说,如果我们把一个数组的元素的按照另一个数组的出现位置做一个数组,那么最长公共子序列就是这个数组的最长上升子序列。

样例举例来说,第二个数组b:21345,元素2在第一个数组a里的下标为1,1的下标是2,……5的下标是4,这样我们就可以得到一个数组:1 2 0 3 4,然后第一个数组下标为1 2 3 4的元素就是第二个数组的最长公共子序列,因为它们的出现顺序是一样的(在a中是上升序列,b中也是)

那么现在问题就从求最长公共子序列变成了就最长上升子序列了。很幸运的是我们恰好知道最长上升子序列的nlogn的写法。

AC代码

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>

#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 100100;
int a[N], b[N], dp[N];
unordered_map<int, int>mymap;
int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        mymap[a[i]] = i;
    }
    for (int i = 0; i < n; i++)
    {
        int x;
        cin >> x;
        b[i] = mymap[x];
    }
    int len = 1;
    dp[0] = -1e7;
    for (int i = 0; i < n; i++)
    {
        int l = 0, r = len;
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (dp[mid] < b[i])l = mid;
            else r = mid - 1;
        }
        len = max(len, r + 1);
        dp[r + 1] = b[i];
    }
    cout << len-1 << endl;
    return 0;
}