CF568E Longest Increasing Subsequence

Longest Increasing Subsequence

给出一个带空缺的长度为 𝑛 的序列,用给出的 𝑚 个数填补空缺(每个数只能使用一次),使得最长(严格)上升子序列最大,输出填补后的序列。

记空缺个数为 𝑘。

𝑛 ≤ 105,𝑘 ≤ 1000,𝑘 ≤ 𝑚 ≤ 105

题解

先考虑不输出方案怎么做。有两种做法:

记录最后一项值为\(i\)的LIS的长度的最大值

先离散化所有数,每个数用几次是不重要的(用超过一次也不会增加 𝐿𝐼𝑆 长度)。

𝑓[𝑖][𝑗] 表示前 𝑖 个数,最后一个数为 𝑗 的 𝐿𝐼𝑆 最长为多少。

如果当前位置不是空缺,那么用树状数组维护转移,否则花 𝑂(𝑚) 暴力转移一下。

直接记录方案的空间承受不了,考虑维护如果这个数在 𝐿𝐼𝑆 中,那么上一个数在哪(不考虑空缺位置)。中间的空缺位置填的数一定是那 𝑚 个数中,数值在上一个数到这个数之间的这些数从小到大排序填进去。

复杂度为 𝑂(𝑛 log (𝑛 + 𝑚) + 𝑚𝑘)。

记录长度为\(i\)的LIS的最后一项的最小值

https://www.luogu.com.cn/blog/xht37/solution-cf568e

\(l_i, p_i\) 分别表示在 \(i\) 不是空缺的位置时,\(1\sim i\) 中包含 \(i\) 的最长上升子序列的长度和上一项的位置。

\(f_i, g_i\) 分别表示长度为 \(i\) 的上升子序列的最后一项的最小值和它所在的位置。

从前往后考虑每一个位置,设此时考虑到位置 \(i\)

若该位置不是空缺的,设该位置上的数为 \(x\),那么可以在 \(f\) 上二分找到 \(<x\) 的最大的 \(f_j\),然后依次更新 \(l_i = j + 1\)\(p_i = g_j\)\(f_{j+1} = x\)\(g_{j+1} = i\)

若该位置是空缺的,考虑从大到小枚举用于填补空缺的数,设枚举到的数为 \(x\),同样可以在 \(f\) 上二分找到 \(<x\) 的最大的 \(f_j\),然后依次更新 \(f_{j+1} = x\)\(g_{j+1} = i\)。这样的时间复杂度为 \(\mathcal O(mk\log n)\),但显然可以一个指针代替二分,时间复杂度优化为 \(\mathcal O((n+m)k)\)

这样就可以直接算出最长上升子序列的长度了,接下来的问题是如何还原这个序列。

显然需要倒序还原,设此时已经还原了最长上升子序列中的第 \(i\) 个数 \(x\),它在原序列的位置为 \(j\)

若该位置不是空缺的,可以直接用 \(p_j\) 找到上一项的位置。

若该位置是空缺的,则先在它前面的不是空缺的位置里找到位置 \(s\) 满足 \(l_s = i - 1\) 同时 \(s\) 上的数 \(< x\)。如果找到了,那么上一项的位置就是 \(s\);否则,上一项的位置就是上一个空缺的位置,且这个空缺上的数为用于填补的数中 \(<x\) 的最大的数。

总时间复杂度 \(\mathcal O(n \log n + m \log m + (n + m)k)\)

CO int N=1e5+10,inf=2e9;
int a[N],b[N];
int f[N],g[N],l[N],p[N];
int v[N],ans[N];

int main(){
	int n=read<int>();
	for(int i=1;i<=n;++i) read(a[i]),f[i]=inf;
	++n,a[n]=f[n]=inf;
	int m=read<int>();
	for(int i=1;i<=m;++i) read(b[i]);
	sort(b+1,b+m+1);
	for(int i=1;i<=n;++i){
		if(a[i]!=-1){
			int j=lower_bound(f+1,f+n+1,a[i])-f-1;
			l[i]=j+1,p[i]=g[j],f[j+1]=a[i],g[j+1]=i;
		}
		else{
			for(int j=n,o=m;o>=1;--o){
				while(f[j]>=b[o]) --j;
				f[j+1]=b[o],g[j+1]=i;
			}
		}
	}
	function<void(int,int,int&)> get=[&](int i,int k,int&x)->void{
		int o=lower_bound(b+1,b+m+1,k)-b-1;
		v[o]=1,x=ans[i]=b[o];
	};
	for(int i=l[n],j=n,x=a[n];i--;){ // inf
		if(a[j]!=-1){
			if(a[p[j]]!=-1) x=a[p[j]];
			else get(p[j],a[j],x);
			j=p[j];
		}
		else{
			bool ok=0;
			for(int s=j-1;s>=1;--s)
				if(a[s]!=-1 and l[s]==i and a[s]<x){
					x=a[j=s],ok=1;
					break;
				}
			if(ok) continue;
			for(int s=j-1;s>=1;--s)
				if(a[s]==-1){
					get(s,x,x),j=s;
					break;
				}
		}
	}
	for(int i=1,j=1;i<=n;++i){
		if(a[i]==-1){
			if(ans[i]) continue;
			while(v[j]) ++j;
			v[j]=1,ans[i]=b[j];
		}
		else ans[i]=a[i];
	}
	for(int i=1;i<n;++i) printf("%d%c",ans[i]," \n"[i==n]);
	return 0;
}

posted on 2020-04-22 11:14  autoint  阅读(207)  评论(0编辑  收藏  举报

导航