P1439「【模板】最長公共子序列」 1. 題目 2. 題解

1. 題目

題目鏈接:P1439「【模板】最長公共子序列」

題目描述

給出 1,2,\ldots,n 的兩個排列 P_1​ 和 P_2​,求它們的最長公共子序列。

輸入格式

第一行是一個數 n

接下來兩行,每行爲 n 個數,爲自然數 1,2,\ldots,n 的一個排列。

輸出格式

一個數,即最長公共子序列的長度。

輸入輸出樣例

輸入 #1
5 
3 2 1 4 5
1 2 3 4 5
輸出 #1
3

說明/提示

  • 對於 50\% 的數據,n \le 10^3

  • 對於 100\% 的數據,n \le 10^5

2. 題解

分析

這是一道 LCS 的模板題,但是如果只用樸素的動態規劃來解,複雜度是 O(n^2),結果終究會 TLE。和 LCS 類似的是 LIS,然而 LIS 有 O(n \log{n}) 的解法,幸運的是部分 LCS 問題可以用 LIS 來解。

  • 在 LCS 中,兩個序列中的元素僅表示一種符號,用來判定是否相等的符號,對於符號背後具體的數值對 LCS 的結果並沒有影響。

  • 在 LIS 中,是需要考慮序列元素的數值大小關係的。

若 LCS 的兩個序列中的其中一個元素互異,則可以用 LIS 來解。在此類 LCS 中,不防假設序列 A 中元素是互異的,設序列 A 的長度爲 n,則我們可以考慮將 A 的元素按照出現順序依次映射到 1 \ldots n 上,從而得到 A 中元素與數值的一一對應關係。然後根據 A 元素的映射表,來計算序列 B 元素對應的映射值;對於在 B 中存在而不在 A 中存在的元素,直接捨棄即可,因爲它們必然不會出現在 LCS 中。如此,映射完 B 得到的數值序列設爲 C,其長度爲 m。則此時只需要計算序列 C 的 LIS 即可。這是因爲序列 A 映射後的序列是一個遞增的數值序列,因此 AC 的公共子序列也是遞增子序列,最長公共子序列也是最長遞增子序列,即序列 C 的最長遞增子序列。

針對這道題而言,由於兩個序列都是 1 \ldots n 的排列,因此可以轉爲 LIS 問題來求解。

代碼

#include <bits/stdc++.h>
using namespace std;

// 最長遞增子序列
struct LIS{
    #ifndef _LIS_
    #define ll int 
    #define MAXN 100005
    #endif 
    ll n;
    ll A[MAXN];
    LIS(): n(0) {}
    // 二分+棧求 LIS 長度
    // 複雜度 O(nlogn)
    ll length() {
        ll cntn = n;
        ll *seqa = A;
        vector <ll> lis;
        lis.push_back(seqa[0]);
        for(ll i = 1; i < cntn; ++i) {
            if(seqa[i] > lis.back()) {
                lis.push_back(seqa[i]);
            } else {
                ll pos = lower_bound(lis.begin(), lis.end(), seqa[i]) - lis.begin();
                lis[pos] = min(lis[pos], seqa[i]);
            }
        }
        return lis.size();
    }
};

int main()
{
    int n;
    int P[MAXN];
    LIS lis;
    scanf("%d", &n);
    lis.n = n;
    for(int i = 0; i < n; ++i) {
        int a;
        scanf("%d", &a);
        P[a] = i; 
    }
    for(int i = 0; i < n; ++i) {
        int b;
        scanf("%d", &b);
        lis.A[i] = P[b];
    }
    printf("%d\n", lis.length());
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章