1. 程式人生 > >動態規劃-最長上升子序列模型

動態規劃-最長上升子序列模型

### 1. 題目描述 給定一個長度為N的數列,求數值嚴格單調遞增的子序列的長度最長是多少。 **輸入格式** 第一行包含整數N。 第二行包含N個整數,表示完整序列。 **輸出格式** 輸出一個整數,表示最大長度。 **資料範圍** $1≤N≤1000$, $−10^9≤數列中的數≤10^9$ **輸入樣例:** ```c++ 7 3 1 2 1 8 5 6 ``` **輸出樣例:** ```C++ 4 ``` ### 2. (DP)樸素解法$O(n^2)$ 本題是一個簡單的DP問題。 令$a[i]$表示陣列中第$i$個數,$f[i]$表示以陣列中第$i$個數結尾的最長上升子序列的長度。 則$f[i] = max(f[j]) + 1,j < i 且a[j] < a[i]$ 程式碼如下: ```c++ #include #include using namespace std; const int N = 1010; int f[N], a[N]; int n; int main() { cin >> n; for(int i = 1; i <= n; i ++) scanf("%d", &a[i]); int res = 0; for(int i = 1; i <= n; i ++) { // 注意初始化f[i] = 1. f[i] = 1; for(int j = 1; j < i ; j ++) if(a[j] < a[i]) f[i] = max(f[i],f[j] + 1); res = max(res, f[i]); } cout << res << endl; return 0; } ``` ### 3. (DP+貪心)優化版本$O(nlgn)$ 通過對本題的觀察與思考。我們可以得到如下的事實: - 對於兩個長度相同的子序列,假設兩個子序列的最後一個值分別是$x_1, x_2$,且$x_1 > x_2$。假設我們的$a[i]$可以放在最後一個值為$x_1$的序列的後面,則其一定能夠放在最後一個值為$x_2$的序列的後面。故,**對於長度相同的序列,我們只需要儲存最後一個值小的序列即可。** 當我們掃描到第$i$個數的時候,可以將其前面的數構成的子序列按長度進行分類,長度為$1$的最長上升子序列只儲存結尾值最小的,長度為$2$的最長上升子序列只儲存結尾值最小的,那麼我們可以證明:**不同長度最長上升子序列最後一個數的值是隨長度嚴格單調遞增的**。 證明: ![](https://img2020.cnblogs.com/blog/2317993/202103/2317993-20210309203349816-301834512.png) 假設長度為$5$的最長上升子序列最後一個數為$a$,長度為$6$的最長上升子序列最後一個數為$b$,倒數第二個數為$c$。 反證法:如果$a>=b$,由於$b > c$,則$a>c$。又由於對於長度相同的子序列我們只儲存最後一個數值較小值。故$a< c$。推出矛盾。 這樣的話,當我們掃描到第$i$個數的時候,就可以通過二分法,找到小於$a[i]$,且最後一個數最大的子序列,將其該數新增到子序列後面。 程式碼如下: ```c++ #include #include using namespace std; const int N = 1010; int n; int a[N]; int q[N]; int main() { cin >> n; for(int i = 0; i < n; i ++)cin >> a[i]; int len = 0; q[0] = -2e9; for(int i = 0; i < n; i ++) { int l = 0, r =len ; while(l < r) // 二分法找出小於等於a[i]的最大的子序列。 { int mid = l + r + 1 >
> 1; if(q[mid] < a[i]) l = mid; else r = mid - 1; } len = max(len, r + 1); // 更新長度 q[r + 1] = a[i]; // 更新末尾值 } cout << len << endl; return 0; } ``` > 注:程式碼中含有部分細節沒有詳細說明,供讀