1. 程式人生 > 實用技巧 >最長公共上升子序列 題解

最長公共上升子序列 題解

原題連結:LCIS

題目大意

給定了兩個長度為\(n,m\)的序列,找出他們的最長公共子序列,要求嚴格上升,只需要長度.例如兩個序列分別是1 1 2 31 2 3那麼答案是1 2 3長度為3.

資料範圍:\(1 \leq n,m \leq 3000\)

思路

顯然從複雜度上來說這個題要求的是一個\(O(n^2)\)的解法.那麼由於這個題本身是來自兩個經典模型LCS和LIS拼接過來的,那麼比較容易的就可以想到這個題所需的幾個關鍵資訊:兩個序列分別走到哪個位置以及當前末尾的是誰.

狀態定義:\(f[i][j]\)表示當前A序列用到了\([1,i]\)而B序列用到了\([1,j]\)且以\(B[j]\)

作為末尾元素的LCIS的所有方案中長度最大值.

入口:全是\(0\),方便起見把\(A[0],B[0]\)記作\(-inf\).

轉移:可以類比原來的兩個模型得到:

  • \(A[i] \neq B[j]\)的時候,只能退而求其次,因為這兩個位置不匹配我就只能拿上一個以\(B[j]\)結尾的狀態轉移,這樣就是最大的不至於更差,也就是說這種情況下\(f[i][j] = f[i - 1][j]\)
  • \(A[i] = B[j]\)的時候,這裡兩個末尾的值匹配起來了,那麼上一個,也就是之前的一個可以拼接過來的狀態,首先應該是用到了A裡的前\(i-1\)個字元,同時是以某個\(k\in[1,j-1],B[k]\)
    作為結尾元素的,那麼同時需要是一個嚴格遞增的序列,所以還要保證\(B[k]<B[j]\).那麼可以寫出\(f[i][j] = \max_{k\in[1,j-1],B[k] < B[j]}f[i-1][k] + 1\)同時由於\(B[k]=A[i]\)那麼也可以替換得到\(f[i][j] = \max_{k\in[1,j-1],B[k] < A[i]}f[i-1][k] + 1\)

出口:\(\max_{i\in[1,m]}f[n][i]\)

那麼上述的狀態計算可以以\(O(n^3)\)的暴力遞推求解.但這顯然不夠,還得要把這個複雜度降掉一維,或者至少也要降一個\(log\)

.那麼考慮如何降掉一維,一個一個來看的話,首先列舉\(i,j\)肯定是避無可避的,想要降只能動這個\(k\).聯絡到之前的一個轉換,即\(B[k]=A[i]\)這一步,不妨把\(j\)進行遞推轉移的時候,看做\(i\)是一個常數,也就是\(A[i]\)是一個常數,現在就不看\(i\)是個什麼情況只看\(j\)了.那麼狀態轉移方程在這一維就是\(f[j] = \max_{k\in[1,j-1],B[k] < C}f[k]+1\),可以注意到很關鍵的一點,那就是既然這個條件是一個小於常數且只取所有滿足條件的值裡的最大值,那麼不妨就把之前可以拿來轉移的最大值記錄下來,進而可以觀察到這個式子的一個特點就是當\(j\)增大的時候,\(k\)的決策集合只會增加一個數,就是說當\(j\)增大的時候,所有\(k\)的取值範圍裡才可能增加一個,這是一個"決策集合裡的元素只增加不減少"的情況,而且只會選取裡面的最大值.因此可以從最開始記錄一個最大值,每個\(j\)增加的時候嘗試把當前一個新的\(k\)放進決策集合,並嘗試更新最大值,如果需要最大值,那麼就直接拿過來用就可以了.由此就可以把整個題的複雜度降到\(O(n^2)\).

在最開始的時候,最大值應該設定成\(1\).因為開始的時候對應過去的元素應該是\(B[0]\),而他顯然是滿足\(B[0]<A[i]\)的.

程式碼

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3005;
int a[N],b[N],f[N][N];
int main()
{
    int n;scanf("%d",&n);
    for(int i = 1;i <= n;++i)   scanf("%d",&a[i]);
    for(int i = 1;i <= n;++i)   scanf("%d",&b[i]);
    
    for(int i = 1;i <= n;++i)
    {
        int maxv = 1;
        for(int j = 1;j <= n;++j)
        {
            f[i][j] = f[i - 1][j];
            if(a[i] == b[j])    f[i][j] = max(f[i][j],maxv);
            if(b[j] < a[i])     maxv = max(maxv,f[i][j] + 1);
        }
    }
    int res = 0;
    for(int i = 1;i <= n;++i)   res = max(res,f[n][i]);
    cout << res;
    return 0;
}