動態規劃-最長上升子序列模型
阿新 • • 發佈:2021-03-09
### 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;
}
```
> 注:程式碼中含有部分細節沒有詳細說明,供讀