【CF568E】Longest Increasing Subsequence(动态规划)

点此看题面

  • 给定一个长度为(n)的序列,其中有(k)个空缺。
  • 你有(m)个数可以用于填补空缺(不能重复使用)。
  • 要求最大化最长上升子序列长度,给出一个方案。
  • (n,mle10^5,kle10^3)

最长上升子序列的套路

考虑最长上升子序列的常见(DP)套路,即设(f_i,g_i)分别表示使得最长上升子序列长度为(i)的最小值及其位置。

然后对于已知的位置,它的值是固定的,我们再额外记(p_i,lst_i)表示以(i)为结尾的最长上升子序列长度以及转移的前一个位置。

转移的时候,已知位置显然可以直接二分转移点转移。

至于未知位置,个数(kle10^3),由于转移点随着填入值的单调变化也是单调移动的,我们可以直接双指针维护转移点转移。

但这道题的难点应该在于方案,而方案的难点在于在最长上升子序列中的未知位置(不在最长上升子序列中的未知位置可以任意填入未填过的数)。

对于一个未知位置,如果能在它前面找到一个长度比它恰少(1)且值比它小的已知位置,那么我们可以认为它是从这个位置转移过来的。

否则,说明它必然是从一个未知位置转移过来的,那么不妨认为它就是从前一个未知位置转移的,显然不会使答案变劣。

代码:(O(nlogn+mk))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define K 1000
using namespace std;
int n,m,a[N+5],b[N+5],c,s[N+5],p[N+5],lst[N+5];
struct S
{
	int x,y;I S(CI a=0,CI b=0):x(a),y(b){}
	I bool operator >= (Con S& o) Con {return x>=o.x;} 
	I bool operator < (Con S& o) Con {return x<o.x;} 
}f[N+5];
int v[N+5];I void Fill(CI x,CI y)//求方案
{
	#define G(x) (lower_bound(b+1,b+m+1,x)-b-1)
	if(y==1) return;RI k=lst[x];if(!k)
	{
		for(k=1;k^x;++k) if(~a[k]&&p[k]==y-1&&a[k]<a[x]) break;if(k==x) W(~a[--k]);//未知位置寻找其转移位置
	}
	return !~a[k]&&(a[k]=b[G(a[x])],v[G(a[x])]=1),Fill(k,y-1);//如果前一个位置未知,则给它一个尽可能大的值
}
int main()
{
	RI i,x,y,w,t=0;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);a[++n]=2e9;//在最后加一个极大值
	for(scanf("%d",&m),i=1;i<=m;++i) scanf("%d",b+i);
	for(sort(b+1,b+m+1),i=1;i<=m;++i) s[i]=b[i];c=unique(s+1,s+m+1)-s-1;//排序去重
	for(i=1;i<=n;++i) if(~a[i])
		f[p[i]=f[t]<a[i]?++t:lower_bound(f+1,f+t+1,a[i])-f]=S(a[i],i),lst[i]=f[p[i]-1].y;//已知位置直接二分转移
	else for(f[t]<s[c]&&(f[++t]=S(s[c],i),0),x=c,y=t;x;f[y]=S(s[x--],i)) W(y^1&&f[y-1]>=s[x]) --y;//双指针维护转移点
	for(Fill(f[t].y,t),i=x=1;i^n;printf("%d ",a[i++])) if(!~a[i]) {W(v[x]) ++x;a[i]=b[x++];}return 0;//输出方案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF568E.html