出自蓝书《算法竞赛入门经典训练指南》
求最长上升子序列是很常见的可以用动态规划解决的问题……
很容易根据最优子结构之类的东西得出
$ ext{dp}[i]$为以第i个数结尾的最长上升子序列长度
定义$max{emptyset}=0$,粗略地写出
[ ext{dp}[i] = max left{ ext{dp}[j]|0leqslant j < i,A[j] < A[i] ight} + 1]
状态数$mathcal{O}({n})$,如果直接枚举转移,转移数$mathcal{O}({n})$,时间复杂度$mathcal{O}({n^2})$
现在想办法加速转移……
设$ ext{dp}^{-1}[x]$为$x= ext{dp}[i]$中$ ext{A}[i]$最小的$i$
设$ ext{pd}[x]= ext{A}[ ext{dp}^{-1}[x]]$
若有$ ext{A}[i]< ext{A}[j]$且$ ext{dp}[i]== ext{dp}[j]$,那么之后的元素只需要比$ ext{A}[i]= ext{pd}[x]= ext{pd}[ ext{dp}[i]]$大就可以用$ ext{dp}[i]$进行转移
很容易得[ ext{pd}[1]leqslant ext{pd}[2]leqslant ext{pd}[3]leqslant cdots leqslant ext{pd}[n] ag{1}label{1} ]
[ ext{dp}[i]=maxleft{x|0leqslant j < i, ext{pd}[x]<A[i] ight}+1]
即最大的小于A[i]的下标加1,也就等价于最小的大于等于A[i]的下标,设为$k$ $ ag{2}label{2}$
因为最后$[l,r)$区间收缩到$emptyset$时左侧区间最后一个元素加一就是右侧区间第一个元素
用STL的lower_bound就不需要自己写二分了
因为$eqref{1}eqref{2}$,所以$A[i]leqslant ext{pd}[k]$,转移以后需要更新$ ext{pd}[k]$
但是之前少考虑了$0leqslant j < i$,只需将未计算的$ ext{pd}[x]$设为INF就好了= =
时间复杂度$mathcal{O}(nlog n)$
代码
REPE(i,1,n) pd[i]=INF; REP(i,0,n) { int k=lower_bound(pd+1,pd+1+n,A[i])-g; dp[i]=k; pd[k]=A[i]; }
很容易得最长非降子序列只需将lower_bound改为upper_bound(同样照着二分的参考图)
然后最长下降子序列只需添加greater<int>()参数,并且初始化为-INF
注意dp为以第i个数结尾的长度,所以求最长还需求一遍max