求最长不升/不降/上升/下降子序列

· · 个人记录

0 前置知识

什么是子序列?

对于一个原始序列,从它里面任意取若干个元素,把它们按照在原始序列里的顺序排列好,得到的新序列就是原始序列的子序列。

下面是一个栗子:

现有一个长度为 n 的序列 A=\{A_1,A_2,\dots,A_n\}A 的任意子序列 B 可表示为

B=\{A_{k_1},A_{k_2},\dots,A_{k_p}\}(1\le k_1<k_2<\dots<k_p\le n,1\le p\le n)

什么是最长不升/不降/上升/下降子序列?

对于一个原始序列,它的一个子序列 S 是最长不升/不降/上升/下降子序列当且仅当:

  1. 其他有性质1的子序列要么没有 S 长,要么与 S 的长度相同。

由定义可看出,一个序列的最长不升/不降/上升/下降子序列可能有多个。

这四种序列的英文表示如下(个人理解):

  1. 最长不升子序列:LNIS(Longest Non-Increasing Subsequence);

  2. 最长不降子序列:LNDS(Longest Non-Decreasing Subsequence);

  3. 最长上升子序列:LIS(Longest Increasing Subsequence);

  4. 最长下降子序列:LDS(Longest Decreasing Subsequence)。

下面是一个栗子:

原始序列

A=\{10,4,9,1,7,2,7,5,7,3,5,0,3,15,1\}\quad(1) $A$ 的 LNDS 长度为 $5$,一个 LNDS 为 $\{4,7,7,7,15\}$。 $A$ 的 LIS 长度为 $5$,一个 LIS 为 $\{1,2,5,7,15\}$。 $A$ 的 LDS 长度为 $6$,一个 LDS 为 $\{10,9,7,5,3,0\}$。 以下将求最长不升/不降/上升/下降子序列的问题统称为最长子序列问题。 # 1 求解算法1:DP 下面以 LNIS 为例,讲解如何用动态规划的方式求解最长子序列问题。 设给定序列 $A$ 长度为 $n$,有一种 $O(n^2)$ 的dp做法。 记 $A_i$ 为给定序列的第 $i$ 个数,$dp_i$ 为**所有以 $A_i$ 结尾的单调不升子序列中最长序列的长度**,初始均为 $1$。不难得出,$dp_i$ 的转移方程为 $dp_i=\max\limits_{1\le j<i,A_j\ge A_i}\left\{dp_j+1\right\}$,LNIS 的长度即为 $\max\limits_{1\le i\le n}\{dp_i\}$。 但这还只是求 LNIS 的长度,如果要将 LNIS 的所有元素都挨个输出该怎么办呢? 首先,记 $last_i$ 为**以 $A_i$ 结尾的所有单调不升子序列中最长序列的倒数第二个元素在 $A$ 里的编号**。 其次,上代码(这部分用语言文字不太好描述): ```cpp int n,m=0,pos,a[maxN],dp[maxN],last[maxN],LNIS[maxN]; //省略了头文件和常数定义这类无关紧要的代码 int main(){ for(n=1;cin>>a[n];n++) dp[n]=1;n--; for(int i=1;i<=n;i++) for(int j=1;j<i;j++) if(a[j]>=a[i] && dp[j]+1>dp[i]) dp[i]=dp[j]+1,last[i]=j; for(int i=1;i<=n;i++) if(dp[i]>m) m=dp[i],pos=i; for(int i=m;i>=1;i--){ LNIS[i]=a[pos]; pos=last[pos]; } cout<<m<<endl; for(int i=1;i<=m;i++) cout<<LNIS[i]<<" "; return 0; } ``` 请结合注释理解一下代码,如果弄懂了如何求最长不升子序列,求其他类型的最长子序列就是手到擒来。 该算法正确性证明略。~~(其实是我不会~~ # 2 求解算法2:贪心+二分 【未完待修】 # 3 例题 # 4 参考文章 李煜东《算法竞赛进阶指南》0x51节 [求最长不下降/上升/下降/不上升子序列](https://www.cnblogs.com/acioi/p/11837911.html) [题解 P1020 【导弹拦截】](/blog/w1049/solution-p1020)