##[$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 ``` ### 二、贪心+二分优化 #### 1、与朴素版本的区别 > **朴素版本状态表示**: > - 集合:$f[i]$表示从第一个数字开始算,以 $a[i]$ 结尾 的最长的上升序列长度。 > - 属性:$max$ 前一版本:$N≤1000$,本题要求:$N≤100000$ $N$的数据范围大了$100$倍,前一版本动态规划代码的时间复杂度是$O(N^2)$,$1000^2=1000000$,是$1e6$,是可以$1$秒过的,但如果是$100000^2=10000000000$,是$1e10$,超时,需要要优化~ #### 2、贪心+二分算法 >核心思想: 对于同样长度的子串,希望它的末端越小越好,这样后面有更多机会拓展它,才有可能使得数列更长。 > **状态表示**: > - 集合:$f[i]$表示长度为$i$的递增子序列中,末尾元素最小的是$f[i]$ > - 属性:$min$ **算法步骤** * 扫描每个原序列中的数字: * 如果$f$中的最后一个数字$f[idx]$小于当前数字$a[i]$,那么就在$f$的最后面增加$a[i]$ * 如果$a[i]$小于$f[idx]$,在$f$中查找并替换第一个大于等于它元素 **举栗子模拟**
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ | | ----- | --- | --- | --- | --- | --- | --- | --- |
开始时$f[]$为空,数字$3$进入序列
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ | | ----- | ---------------------------- | --- | --- | --- | --- | --- | --- | | $f$ | $3$ | | --- | ------------------------------------------ |
$1$ 比 $3$ 小, $3$出序列 ,$1$入序列
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ | | ----- | --- | ------------------------------ | --- | --- | --- | --- | --- | | $f$ | $1$ | | --- | ------------------------------------------ |
$2$ 比 $1$ 大,$2$入序列
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ | | ----- | --- | --- | ----------------------------- | --- | --- | --- | --- | | $f$ | $1$ | $2$ | | --- | --- | ------------------------------------------ |
$1$ 比 $2$ 小,在$f$中找到第一个大于等于$1$的位置,并替换掉原来的数字
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ | | ----- | --- | --- | --- | ---------------------------- | --- | --- | --- | | $f$ | $1$ | $2$ | | --- | ------------------------------------------ | --- |
$8$ 比 $2$ 大
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ | | ----- | --- | --- | --- | --- | ---------------------------- | --- | --- | | $f$ | $1$ | $2$ | $8$ | | --- | --- | --- | ------------------------------------------ |
$5$ 比 $8$ 小,在$f$中找到第一个大于等于$5$的数字,并替换掉原来的数字
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ | | ----- | --- | --- | --- | --- | --- | ---------------------------- | --- | | $f$ | $1$ | $2$ | $5$ | | --- | --- | --- | ------------------------------------------ |
$6$ 比 $5$ 大
| $arr$ | $3$ | $1$ | $2$ | $1$ | $8$ | $5$ | $6$ | | ----- | --- | --- | --- | --- | --- | --- | ------------------------------------------- | | $f$ | $1$ | $2$ | $5$ | $6$ | | --- | --- | --- | --- | ------------------------------------------ |
$f$ 的长度$idx$就是最长递增子序列的长度
#### 3、时间复杂度 $$\large O(N*logN)$$ ### 四、实现代码 ```cpp {.line-numbers} #include 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; } ```