LIS 普及题

题意

  给你一个长度为 \(n\) 的序列 \(a\)
  问是否存在一个长度为 \(L\) 的上升子序列,即存在 \(\{x_1,x_2,...,x_L\}(x_1\lt x_2\lt ...\lt x_L)\),使得 \(a_{x_1}\lt a_{x_2}\lt ...\lt a_{x_L}\)
  输出 \(\{a_{x_1}, a_{x_2}, ..., a_{x_L}\}\),若存在多组解,输出 \(\{a_{x_i}\}\) 字典序最小的一组。

题解

  设 \(f[i]\) 表示以 \(i\) 为起点的最长上升子序列的长度是多少。这个显然可以倒推序列 \(a\),用朴素的求 LIS 的方法求出。
  然后把序列 \(a\)\(a_i\) 从小到大为第一关键字,下标 \(i\) 从小到大为第二关键字排序。显然从前往后贪心选取即可。
  具体地,我们依次确定答案序列的每一位。设答案序列还有 \(L\) 位未确定,当扫到 \(a\) 序列的第 \(i\) 位时,若同时满足以下 \(3\) 个条件 $$\begin{cases} a_i\gt 答案序列上一个确定的数 \ i\gt 答案序列上一个确定的位 \ f[i]\ge L \end{cases}$$

  则将这个 \(a_i\) 确定为答案序列的下一位显然是最优的。
  这样就能保证答案序列越靠前的位越尽量小,字典序也就最小了。
  复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define N 100010
using namespace std;
inline int read(){
	int x=0; bool f=1; char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
	for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
	if(f) return x; return 0-x;
}
int n,L,dp[N],g[N],mx;
struct data{int v,pos;}a[N];
inline bool cmp(data a, data b){return a.v==b.v ? a.pos<b.pos : a.v<b.v;}
int binary(int x){
	int l=1, r=mx, mid, ans=0;
	while(l<=r){
		int mid=l+r>>1;
		if(g[mid]>x) ans=mid, l=mid+1;
		else r=mid-1;
	}
	return ans;
}
int main(){
	n=read(), L=read();
	for(int i=1; i<=n; ++i) a[i].v=read(), a[i].pos=i;
	for(int i=n; i>0; --i){
		dp[i]=binary(a[i].v)+1;
		mx=max(mx,dp[i]);
		g[dp[i]]=max(g[dp[i]],a[i].v);
	}
	if(mx<L){printf("impossible\n"); return 0;}
	sort(a+1,a+n+1,cmp);
	int lstpos=0,lstv=0;
	for(int i=1; i<=n; ++i)
		if(dp[a[i].pos]>=L && a[i].pos>lstpos && a[i].v>lstv){
			printf("%d ",a[i].v);
			if(--L==0) return 0;
			lstpos=a[i].pos;
			lstv=a[i].v;
		}
	return 0;
}
posted @ 2019-08-13 18:16  大本营  阅读(224)  评论(0)    收藏  举报