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;
}