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 的 字典序最小的开头, 再以此类推。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值