動態規劃法求最長上升子序列(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> usingnamespace 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。