最长上升子序列(longest increasing subsequence,LIS),上升即严格单调递增;不严格单调递增(前≤后)叫做不下降.
先是朴素的平方时间做法. 设
for (int i=1; i<n; i++) {
for (int j=0, m=0; j<i; j++)
if (a[j] < a[i] && f[j] > f[m])
m=j;
f[i]=f[j]+1;
}
可以发现有一些无用的转移. 比如说, 已经遍历
解决方法是,不根据元素来设 dp,而根据 LIS 的长度来设. 设
for (int i=0; i<n; i++) {
std::cin >> a[i];
int l=0, r=tot, m;
while (l<r) {
m=l+(r-l>>1);
if (f[m]<a[i]) l=m+1;
else r=m;
}
f[r]=a[i], tot += (r==tot);
这道题要求输出方案,只需要记录转移,输出时顺着记录找即可. 设 pos[i]
表示 f[i]
在原数组的下标,pre[i]
表示 a[i]
所在的 LIS 的前一位. 这样子仍然能求出以 a[i]
为结尾的 LIS,因为 dp 完成后 pre
实际上记录了这样的树:
input: [3,1,4,1,5,9,2,6,5,3,5,8,9,7]
3
1-4-5-9
\2 \6
|\5
\-3-5-8-9
\7-9
output: [1,2,3,5,8,7,9]
从上图可以发现,这个方法输出的 LIS 一定是字典序最小的. AC code:
#include <iostream>
const int N=100006;
int n, a[N], f[N], pos[N], pre[N], tot;
int main() {
std::cin >> n;
for (int i=0; i<n; i++) {
std::cin >> a[i];
int l=0, r=tot, m;
while (l<r) {
m=l+(r-l>>1);
if (f[m]<a[i]) l=m+1;
else r=m;
}
f[r]=a[i], tot += (r==tot);
pos[r]=i;
pre[i]=(r ? pos[r-1] : -1);
}
for (int p=pos[tot-1], i=tot; ~p; p=pre[p])
f[--i]=a[p];
for (int i=0; i<tot; i++)
std::cout << f[i] << " \n"[i+1==tot];
}