1. 程式人生 > >動態規劃法求最長上升子序列(LIS)

動態規劃法求最長上升子序列(LIS)

最長上升子序列(LIS)是指一個序列中最長的單調遞增的子序列,對於任意的i<j都滿足ai<aj的子序列。

下面我們來介紹兩種dp來求LIS。

方法1:

我們首先來建立一下遞推關係:

定義dp[i]:為以ai為末尾的最長上升子序列的長度。

ai 結尾的上升子序列是:

(1)只包含 ai 的子序列

(2)在滿足 j<i 並且 aj<ai 的以 aj 為結尾的上升子序列末尾,追加 ai 後得到的子序列

這二者之一。這樣就可以建立如下遞推關係:

dp[i]=max{1,dp[j]+1 j<i且 aj<ai };

程式碼如下:

#include<bits/stdc++.h>
using
namespace std; const int maxn=1000005; int dp[maxn]; int a[maxn]; int main() { int n; int res = -1; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) { dp[i] = 1; for (int j = 1; j < i; j++) {
if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1); } res=max(res,dp[i]); } cout << res; return 0; }

 

這個演算法的複雜度為O(n2),當資料量過多時會超時,下面介紹複雜度為O(nlogn)的演算法。

方法2:

 首先我們來定義dp[k]:長度為k的上升子序列的最末元素,若有多個長度為k的上升子序列,則記錄最小的那個最末元素。
注意dp中元素是單調遞增的,我們在下面要用到這個性質

len為LIS的長度,首先讓len=1,dp[1]=a[1], 然後i從2開始,對於a[i],如果

(1) a[i]>dp[len],那麼此時可以直接把a[i]接到d[len]的後面,並且長度加一。即d[++len]=a[i]。

(2) a[i]<dp[len],那麼我們就在dp這個陣列中找到第一個比a[i]大

的數,並用a[i]替換這個數,此時陣列dp仍然保持遞增且長度不變。

這樣我們就維護了陣列dp,並且最後的len就是最長上升子序列的長度。演算法複雜度為O(nlogn)。

程式碼如下:

 

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
int dp[maxn];
int a[maxn];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int len=1;
    dp[1]=a[1];
    for (int i = 2; i <= n; i++)
    {
           if (a[i] > dp[len])
             dp[++len]=a[i];
           else{
            int j=lower_bound(dp+1,dp+len+1,a[i])-dp;
            dp[j]=a[i]; 
         }
                
    }
    cout<<len;
    return 0;
}    

 

下面再補充一個求最長不上升子序列的方法,複雜度同樣為O(nlogn)。

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000005;
int a[maxn], dp[maxn];
bool cmp(int a, int b)
{
    return a > b;
}
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int len = 1;
    dp[1] = a[1];
    for (int i = 2; i <= n; i++)
    {
        if (dp[len] >= a[i])
            dp[++len] = a[i];
        else 
            dp[upper_bound(dp + 1, dp + len + 1, a[i], cmp) - dp] = a[i];
    }
    cout << len;
    return 0;
}

 

這裡由於dp數組裡的數遞減的,而upper_bound適用於遞增數列,因此我們給他過載了比較函式。

這樣當 a[i]>dp[len] 時,我們在dp陣列中找到最後一個大於a[i]的數(因為非上升子序列,可能會存在相同的數,用upper比較好一點),並替換它。

如果dp裡沒有比a[i]大的數,就會返回第一個數,這時a[i]替換的就是dp的第一個數,同樣滿足dp是遞減的陣列。

這樣我們就得到了最長不上升子序列的長度len。