LIS 求最长上升子序列并输出字典序最小的解
// An highlighted block
#include <cstdio>
#include <algorithm>
#include <stack>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <cstring>
#define inf 2147483645
using namespace std;
int n, a[100005], dp[100005], poss[100005], len, last, fir[100005], las[100005];
stack <int> s;
int main()
{
cin>>n;
for (int i=1; i<=n; i++)
cin>>a[i];
for (int i=1; i<=n; i++) dp[i] = inf;
dp[1] = a[1], len = 1; poss[1] = 1; last = 1; fir[1] = las[1] = 1;
for (int i=2; i<=n; i++) {
if (a[i] > dp[len]) {
poss[i] = ++len;
fir[len] = las[len] = i;
dp[len] = a[i];
last = i;
} else {
poss[i] = lower_bound(dp+1, dp+1+len, a[i]) - dp;
dp[poss[i]] = a[i];
las[poss[i]] = i;
}
}
cout<<len<<endl;
for (int i=len-1; i>=1; i--){
for (int j=las[i+1]; j>=1; j--){
if (poss[j] == i) {
las[i] = j;
break;
}
}
}
int p = fir[1]; int minn;
for (int i=1; i<=len; i++){
minn = 2147483640;
for (int j=p; j<=las[i]; j++){
if (poss[j] == i && a[j] < minn){
p = j;
minn = a[j];
}
}
cout<<minn<<" ";
}
/* s.push(a[last]);
for (int i=len-1; i>=1; i--){
for (int j=last-1; i; i--){
if (poss[j] == i) {
last = j;
s.push(a[last]);
break;
}
}
}
while (!s.empty()){
cout<<s.top()<<" ";
s.pop();
} */
return 0;
}
las[i] 记录在求LIS 过程中出现在 DP[i] 位置的最后一个原序列中 a[x] 的数组下标x. fir[ ]同理。
-
S1: 从长到短筛选合法的 las[i] ( 1…i…len-1 ) , 即在 las[i] 位置之后一定存在一系列 比 poss[i] 大的数。
例如 : 原序列 a[] : 2 6 8 2 5 9 4 7
poss[]: 1 2 3 1 2 4 2 3
lis_len = 4
fir[]: 1 2 3 6
初始las[]: 4 7 8 6
显然 las[3] = 8 时 后面没有 poss[i] = 4, 所以 a[8] 是非法的。
修改后的 las[] : 1 2 3 6
一定要从大到小修改)) : 这样可以保证从 fir[i] 到 las[i] 的闭区间里的任意一个 poss[] 为 i 的元素开头都存在一个合法的最长上升子序列。 -
S2: 从 fir[1] 到 las[1] 中找到 poss[] 为 1 的 字典序最小的开头, 再以此类推。