Skip to content

LCS 和 LIS

最長共同子序列 (Longest Common Subsequence)

最長共同子序列

子序列是指一個序列去除任意個(包含 0)元素後所形成的新序列。 給定兩序列 ,求最長的序列 同時為 的子序列。

  • 狀態: 表示使用 的 LCS 長度。
  • 初始狀態: when
  • 轉移:

求出 LCS 的時間、空間複雜度都是 ,使用滾動陣列技巧,可將空間複雜度降至

最長遞增子序列 (Longest Increasing Subsequence)

最長遞增子序列

給你一個序列 ,求最長的序列 是一個(非)嚴格遞增序列,且為 的子序列。

  • 狀態: :第 個數字所形成最長遞增子序列的長度
  • 轉移:
  • 初始化:

求出 LIS 的時間複雜度 ,空間複雜度

LCS 和 LIS 題目轉換

  • LIS 轉成 LCS
    • 為原序列,
    • 做 LCS
  • LCS 轉成 LIS
    • 為原本的兩序列
    • 序列作編號轉換,將轉換規則套用在
    • 做 LIS
    • 重複的數字在編號轉換時後要變成不同的數字,越早出現的數字要越小
    • 如果有數字在 裡面而不在 裡面,直接忽略這個數字不做轉換即可

LIS 的時間複雜度從 降至

LIS 還有另一種狀態轉移式:

  • 狀態: 表示使用前 個數字湊出長度 的 LIS, 末端數字最小為何。
  • 轉移:
  • 初始狀態: when

這樣的狀態轉移式時間和空間複雜度依舊是 ,不過,有幾點值得觀察:

, where ,忽略 為一個嚴格遞增序列,當 每次 每次都有一個數字改變,改變的數字皆為 ,被改的數字為

根據上述性質,可用二分搜更新 ,藉此找到 LIS 的長度。每次二分搜的時間複雜度為 ,整體時間複雜度為

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int main()
{
    int n;
    while (cin >> n)
    {
        vector<int> v;
        for (int i = 0, x; i < n; i++)
        {
            cin >> x;
            if (!v.size() || x > v.back())
                v.push_back(x);
            else
                *lower_bound(v.begin(), v.end(), x) = x;
        }
        cout << v.size() << '\n';
    }
}

例題練習