You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
5.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##[$AcWing$ $896$. 最长上升子序列 II](https://www.acwing.com/problem/content/description/898/)
### 一、题目描述
给定一个长度为 $N$ 的数列,**求数值严格单调递增的子序列的长度最长** 是多少。
**输入格式**
第一行包含整数 $N$。
第二行包含 $N$ 个整数,表示完整序列。
**输出格式**
输出一个整数,表示最大长度。
**数据范围**
$1≤N≤100000$
$10^9≤数列中的数≤10^9$
**输入样例:**
```cpp {.line-numbers}
7
3 1 2 1 8 5 6
```
**输出样例**
```cpp {.line-numbers}
4
```
<!-- 让表格居中显示的风格 -->
<style>
.center
{
width: auto;
display: table;
margin-left: auto;
margin-right: auto;
}
</style>
### 二、贪心+二分优化
#### 1、与朴素版本的区别
> **朴素版本状态表示**
> - 集合:$f[i]$表示从第一个数字开始算,以 $a[i]$<font color='red'><b> 结尾 </b></font>的最长的上升序列长度。
> - 属性:$max$
前一版本:$N≤1000$,本题要求:$N≤100000$
$N$的数据范围大了$100$倍,前一版本动态规划代码的时间复杂度是$O(N^2)$,$1000^2=1000000$,是$1e6$,是可以$1$秒过的,但如果是$100000^2=10000000000$,是$1e10$,超时,需要要优化~
#### 2、贪心+二分算法
><font color='red' size=4><b>核心思想:
对于同样长度的子串,希望它的末端越小越好,这样后面有更多机会拓展它,才有可能使得数列更长。</b></font>
> **状态表示**
> - 集合:$f[i]$表示长度为$i$的递增子序列中,末尾元素最小的是$f[i]$
> - 属性:$min$
**算法步骤**
* 扫描每个原序列中的数字:
* 如果$f$中的最后一个数字$f[idx]$小于当前数字$a[i]$,那么就在$f$的最后面增加$a[i]$
* 如果$a[i]$小于$f[idx]$,在$f$中查找并替换第一个大于等于它元素
**举栗子模拟**
<div class="center">
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ |
| ----- | --- | --- | --- | --- | --- | --- | --- |
</div>
开始时$f[]$为空,数字$3$进入序列
<div class="center">
| $arr$ | <font color='red'>$3$</font> | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ |
| ----- | ---------------------------- | --- | --- | --- | --- | --- | --- |
| $f$ | <font color='red' size=3><b>$3$</b></font> |
| --- | ------------------------------------------ |
</div>
$1$ 比 $3$ 小, $3$出序列 $1$入序列
<div class="center">
| $arr$ | $3$ | <font color='red'> $1$ </font> | $2$ | $1$ | $8$ | $5$ | $6$ |
| ----- | --- | ------------------------------ | --- | --- | --- | --- | --- |
| $f$ | <font color='red' size=4><b>$1$</b></font> |
| --- | ------------------------------------------ |
</div>
$2$ 比 $1$ 大,$2$入序列
<div class="center">
| $arr$ | $3$ | $1$ | <font color='red'>$2$ </font> | $1$ | $8$ | $5$ | $6$ |
| ----- | --- | --- | ----------------------------- | --- | --- | --- | --- |
| $f$ | $1$ | <font color='red' size=4><b>$2$</b></font> |
| --- | --- | ------------------------------------------ |
</div>
$1$ 比 $2$ 小,在$f$中找到第一个大于等于$1$的位置,并替换掉原来的数字
<div class="center">
| $arr$ | $3$ | $1$ | $2$ | <font color='red'>$1$</font> | $8$ | $5$ | $6$ |
| ----- | --- | --- | --- | ---------------------------- | --- | --- | --- |
| $f$ | <font color='red' size=4><b>$1$</b></font> | $2$ |
| --- | ------------------------------------------ | --- |
</div>
$8$ 比 $2$ 大
<div class="center">
| $arr$ | $3$ | $1$ | $2$ | $1$ | <font color='red'>$8$</font> | $5$ | $6$ |
| ----- | --- | --- | --- | --- | ---------------------------- | --- | --- |
| $f$ | $1$ | $2$ | <font color='red' size=4><b>$8$</b></font> |
| --- | --- | --- | ------------------------------------------ |
</div>
$5$ 比 $8$ 小,在$f$中找到第一个大于等于$5$的数字,并替换掉原来的数字
<div class="center">
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | <font color='red'>$5$</font> | $6$ |
| ----- | --- | --- | --- | --- | --- | ---------------------------- | --- |
| $f$ | $1$ | $2$ | <font color='red' size=4><b>$5$</b></font> |
| --- | --- | --- | ------------------------------------------ |
</div>
$6$ 比 $5$ 大
<div class="center">
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | <font color='red' size=4><b>$6$ </b></font> |
| ----- | --- | --- | --- | --- | --- | --- | ------------------------------------------- |
| $f$ | $1$ | $2$ | $5$ | <font color='red' size=4><b>$6$</b></font> |
| --- | --- | --- | --- | ------------------------------------------ |
</div>
<font color='blue' size=6><b><center>$f$ 的长度$idx$就是最长递增子序列的长度</center></b></font>
#### 3、时间复杂度
$$\large O(N*logN)$$
### 四、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, a[N];
// 数组模拟栈
int f[N], fl;
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
// 1、首个元素入栈
f[0] = a[0];
// 2、后续元素开始计算
for (int i = 1; i < n; i++) {
if (a[i] > f[fl])
f[++fl] = a[i];
else
// 利用STL的二分在f中查找第一个大于等于a[i]的值,并完成替换
*lower_bound(f, f + fl, a[i]) = a[i];
}
// 栈内元素数量就是答案
printf("%d\n", fl + 1);
return 0;
}
```