[CF568E] Longest Increasing Subsequence

发布时间 2023-10-09 11:36:45作者: 灰鲭鲨

题目描述

Note that the memory limit in this problem is less than usual.

Let's consider an array consisting of positive integers, some positions of which contain gaps.

We have a collection of numbers that can be used to fill the gaps. Each number from the given collection can be used at most once.

Your task is to determine such way of filling gaps that the longest increasing subsequence in the formed array has a maximum size.

输入格式

The first line contains a single integer $ n $ — the length of the array ( $ 1<=n<=10^{5} $ ).

The second line contains $ n $ space-separated integers — the elements of the sequence. A gap is marked as "-1". The elements that are not gaps are positive integers not exceeding $ 10^{9} $ . It is guaranteed that the sequence contains $ 0<=k<=1000 $ gaps.

The third line contains a single positive integer $ m $ — the number of elements to fill the gaps ( $ k<=m<=10^{5} $ ).

The fourth line contains $ m $ positive integers — the numbers to fill gaps. Each number is a positive integer not exceeding $ 10^{9} $ . Some numbers may be equal.

输出格式

Print $ n $ space-separated numbers in a single line — the resulting sequence. If there are multiple possible answers, print any of them.

1.5s,128MB

LIS 问题有两种优化方式,所以这题也有两种做法。

1. 二分

考虑用二分维护 \(dp_{i}\) 表示目前长度为 \(i\) 的 序列里面树最小的是 \(i\),然后对于固定的数二分去修改,不固定的可以把 \(m\) 个数排序后维护一个指针扫过去找到第一个大于 \(a_i\) 的,这里复杂度 \(nk\)

但是要输出方案,而且不可能开的下 \(nk\) 的区间去记录每个点的前驱。对于每个正常的数仍然记录下他的前驱是哪个位置。设现在第 \(x\) 个位置是 LIS 中的第 \(p\) 个数,填了 \(y\)。那么如果 \(a_x\ne -1\),LIS 中 \(p-1\) 个数一定是它的前驱,否则我们就先找一下是否存在一个 \(dp\) 值为 \(p-1\) 并且 \(a_j\ne -1\)\(j\),还要满足 \(a_j<y\),如果存在 \(p-1\) 位可以填他,否则 \(p-1\) 位就是上一个 -1 出现的地方。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int lst[N],mx,id[N],n,k,m,a[N],b[N],dp[N],ls[N],fr[N];//ls是值,fr 是位置
multiset<int>s;
vector<int>g[N];
int read()
{
	int s=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
		s=s*10+ch-48,ch=getchar();
	return s*f;
}
void sou(int x,int y,int p)//在 x 处,填了y,子序列第p项
{
	//printf("%d %d %d\n",x,y,p);
	if(p==1)
	{
		if(a[x]==-1)
		{
			a[x]=y;
			s.erase(s.lower_bound(y));
		}
		return;
	}
	if(a[x]==-1)
	{
		a[x]=y;
		s.erase(s.lower_bound(y));
		vector<int>::iterator k=lower_bound(g[p-1].begin(),g[p-1].end(),x);
		if(k==g[p-1].begin()||a[*(k-1)]>=y)
			sou(lst[x],*(lower_bound(b+1,b+m+1,y)-1),p-1);
		else
			sou(*(k-1),a[*(k-1)],p-1);
	}
	else
		sou(ls[x],fr[x],p-1);
}
int main()
{
	memset(dp,0x7f,sizeof(dp));
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	m=read();
	for(int i=1;i<=m;i++)
		s.insert(b[i]=read());
	sort(b+1,b+m+1);
	for(int i=1;i<=n;i++)
	{
		if(!~a[i-1])
			lst[i]=i-1;
		else
			lst[i]=lst[i-1];
		if(~a[i])
		{
			int t=lower_bound(dp+1,dp+n+1,a[i])-dp;
			dp[t]=a[i],id[t]=i;
			ls[i]=id[t-1],fr[i]=dp[t-1];
			g[t].push_back(i);
			//printf("%d %d\n",i,t);
		}
		else
		{
			int r=n;
			for(int j=m;j;j--)
			{
				while(r^1&&dp[r-1]>=b[j])
					--r;
				dp[r]=b[j],id[r]=i;
			}
		}
	}
	for(int i=1;i<=n;i++)
		if(dp[i+1]==0x7f7f7f7f)
			mx=i,i=n;
	//printf("%d\n",mx);
	sou(id[mx],dp[mx],mx);
	for(int i=1;i<=n;i++)
	{
		if(!~a[i])
		{
			a[i]=*s.begin();
			s.erase(s.begin());
		}
	}
	for(int i=1;i<=n;i++)
		printf("%d ",a[i]);
}

2.数据结构

定义 \(dp_i\) 为以 \(i\) 为结尾的 LIS 最长是多少,这里我们忽略掉 -1 的存在。
\(c_i\) 为可以填的数中有多少个不超过 \(i\) 的数,\(a_i\) 为原数组中前 \(i\) 个数有多少个 -1

然后 \(dp_i=\max\limits_{j<i}dp_j+\min(c_{a_i}-c_{a_j},s_i-s_j)\)
把 min 拆掉,就变成了一个三维偏序,CDQ 分治即可。
方案可以通过记录 CDQ 时线段树上的最大值来源就行了。
代码懒得写了