题解 CF568E 【Longest Increasing Subsequence】

CF通道

luogu链接


大致题意

给你一个长度为n的序列,这其中可能有-1,代表着空位

然后给你m个数,代表空位可选的m个数

然后叫你补全序列(每个\(b[i]\)只能用一遍),是补全后得到的序列LIS(严格)最大

输入格式:

\(n\)

\(a_1\;a_2\;a_3\cdots\;a_n\)

\(m\)

\(b_1\;b_2\;b_3\cdots\;b_m\)


思路分析

因为这道题是叫我们求LIS的构造方案,我们(理所应当)应该想到LIS的求的方法。

  1. 方法1:循环枚举法(\(n^2\)

这个方法是利用双重\(for\)循环,然后进行LIS的判断

//LIS[i]代表的是以i为终点的LIS的最大长度
for(register int i=1;i<=n;i++)
	for(register int j=0;j<i;j++)
    	if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1);
  1. 方法2:利用辅助数组\(f[i]\)进行LIS(\(n\times logn\))

\(f[x]\)代表的是长度为x的LIS的末尾项最小是多少,而我们每一次遍历到\(i\)时,我们都可以进行二分查找,找到小于\(a[i]\)的最大的f[x],然后赋值\(f[x+1]=a[i]\)

//f[i]意义同上
//ans代表的是最长上升子序列
memset(f,0x3f3f3f3f3f,sizeof(f));
for(register int i=1;i<=n;i++){
	int j=lower_bound(f+1,f+1+n,a[i])-f-1;
    f[j+1]=a[i];
    ans=max(ans,j+1);
}

从复杂度讲

\[n \leq 10^5 \]

还是从是否有空位讲

我们都应该选择从第二个方法入手。

显而易见,我们应该把空位和数字分开处理。

  • 数字

对于数字来说,我们就按照上面的LIS一样处理就好了

  • 空位

对于空位来说,我们应该在这个位置上枚举每一个\(b[i]\),然后分别像数字一样用LIS。


感觉上是不是弄好了,但是有一点值得注意的是,答案不是叫我们输出可能得到的最大LIS的长度,而是叫我们输出修改后的序列,使这个序列里面有可能长度最大的LIS,所以我们还有两个问题亟需解决。

  1. 题目中要求每个\(b[i]\)只能用一遍

  2. 如何输出我们求到的序列


\(\color{red}{Solution :}\)

  1. 关于只能用一遍

因为如果我们在两个不同的空位用上相同的位置,这两个位置之间没有办法做出贡献(因为要求的是严格),所以即使我们这两个点都是相同的,那么对于前面的序列,贡献都一样,而互相没有贡献,所以这个限制我们可以忽略。

  1. 关于输出

我们要输出这个序列,如果是正序枚举然后挨个挨个向后推的话显然是没法的。

原因:

因为你用LIS求出来的永远都只是以这个点为终点可以构造出来的最长LIS,所以你遍历到这个点可以保证在这个点之前可以构造LIS,可是如果你从前往后遍历的话,你根本不知道你现在构造出来的序列是不是你要求的最终序列,所以只能从后往前构造,而不能从前往后构造。


所以我们选择从后往前构造,现在问题又来了,请问你遍历到一个点后,你打算跳到哪个点呢?

这时候我们应该增加两个辅助数组,一个是\(g[i]\)代表的是以i为结束点的LIS长度是多少,一个\(h[i]\)代表的是以i为结束点的LIS的上一个点是多少。

而且我们必须保证i不为空位

为什么呢?

因为我们对于空位的处理方法是枚举这m个数,然后挨个挨个去更新,那么最坏情况下这个位置上有m个不同的LIS,那么违反了我们两个数组的定义,所以我们不能算上空格。

所以:

  1. 位置不是空位

\(h[i]\)就好了

  1. 位置是空位

那么就先找有没有不是空位的数可以构造出剩下的LIS,不然的话就用最近的空格。

(因为我们保证了每个数我们都清楚以他为终点的LIS的最大长度,所以我们可以先找确定的点,这样的话我们就省下了一个填补的数,而如果非得用空格的话,那就选最近的空格,这也是一个贪心的思想)


基于上面的思想,我们就可以打代码了(具体细节还得看代码)

\[\color{blue}{Talk\;is\;cheap,show\;the\;code\;!} \]

#include<cstdio>
#include<iostream>
#include<algorithm>
#define ri register int
#define Starsseven main
using namespace std;
const int N=1e5+20;
const int inf=999999999;
int a[N],b[N];

inline int read() {
    int X=0,w=1; char c=getchar();
    while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
    while (c>='0'&&c<='9') X=X*10+c-'0',c=getchar();
    return X*w;
}

int n,m,ans[N];
int Lis_minn[N]/*这个数组[i]表示的是LIS长为i时结尾最小的数*/,Lis_this[N]/*表示的是以i结尾(i不是空位)的LIS长度*/;
int Lis_last[N]/*这个数组表示的是长度为i的LIS的结尾最小值位置在哪里*/,Lis_before[N]/*表的是以i(i不是空位)为结尾的LIS的上一项*/;
 

bool vis[N];
void Find(int i,int h,int &x){
	int j=lower_bound(b+1,b+1+m,h)-b-1;
	vis[j]=1;x=ans[i]=b[j];
}
//Find(Lis_before[j],a[j],x)
int main(void){
	n=read();//表示有n个数构成的序列 
	for(ri i=1;i<=n;i++) a[i]=read();//读入a[i] 
	n++;a[n]=inf;
	m=read();//表示有m个可选的数 
	for(ri i=1;i<=m;i++) b[i]=read();//表示这m个数的大小
	sort(b+1,b+1+m);//将b[]从小到大排序,因为我们之后要用到的是二分出最小的b[i]可以替换
	for(ri i=1;i<=n;i++) Lis_minn[i]=inf;//先初始化minn数组 
	//int Lis_minn[N]/*这个数组[i]表示的是LIS长为i时结尾最小的数*/,Lis_this[N]/*表示的是以i结尾(i不是空位)的LIS长度*/;
	//int Lis_last[N]/*这个数组表示的是长度为i的LIS的结尾最小值位置在哪里*/,Lis_before[N]/*表的是以i(i不是空位)为结尾的LIS的上一项*/;
	for(ri i=1;i<=n;i++)/*从前往后枚举a[i]*/{
		if(a[i]!=-1){//表示这个数不是空位 
			int j=lower_bound(Lis_minn+1,Lis_minn+1+n,a[i])-Lis_minn-1;//这个表示的是找到可以更新得到的数组
			/*因为lower_bound求的是Lis_minn中小于a[i]的最大值,这样就可以保证:
				在j之后的数a[i]更新不了
				而在j之前的数,因为a[i]>Lis_minn[j],如果更新j-1则绝对不是"最小值"*/
			Lis_minn[j+1]=a[i];Lis_this[i]=j+1;
			Lis_last[j+1]=i; Lis_before[i]=Lis_last[j];
		}
		else{//表示这就是空位 
			for(ri j=m,x=n;j;j--){
				while(Lis_minn[x]>=b[j]) --x;//找到b[j]可以更新的长度为x-1的数,为什么不更新前面的和后面的同理同上
				Lis_minn[x+1]=b[j];
				Lis_last[x+1]=i;
			}
		}
	}
	//下面是输出答案
	ri i=Lis_this[n],j=n,x=a[j];
	while(i--){
	//int Lis_minn[N]/*这个数组[i]表示的是LIS长为i时结尾最小的数*/,Lis_this[N]/*表示的是以i结尾(i不是空位)的LIS长度*/;
	//int Lis_last[N]/*这个数组表示的是长度为i的LIS的结尾最小值位置在哪里*/,Lis_before[N]/*表的是以i(i不是空位)为结尾的LIS的上一项*/;
		if(a[j]!=-1){
			if(a[Lis_before[j]]==-1) Find(Lis_before[j],a[j],x);
			else x=a[Lis_before[j]];
			j=Lis_before[j];
		}
		else{
			bool Judge=false;
			for(ri s=j-1;s;s--)
				if(a[s]!=-1&&Lis_this[s]==i&&a[s]<x){
					x=a[j=s],Judge=1;break;
				}
			if(Judge) continue;	
			for(ri s=j-1;s;s--){
				if(a[s]==-1){
					Find(s,x,x),j=s;
					break;
				}
			}
		}
	}
	for(ri i=1,j=1;i<=n;i++){
		if(a[i]==-1){
			if(ans[i]) continue;
			while(vis[j]) j++;
			vis[j]=1,ans[i]=b[j];
		}
		else ans[i]=a[i];
	}
	for(ri i=1;i<n;i++) printf("%d ",ans[i]); 
	return 0;
} 
posted @ 2020-06-12 14:51  starseven  阅读(124)  评论(0编辑  收藏  举报