传送门:http://codeforces.com/problemset/problem/568/E
思路:首先没有空位,我们是记录一个low数组表示长度为i的上升子序列的最小结尾。
对于一个末尾新的数x,我们只要二分出一个位置low[i]<x<=low[i+1],即可更新low[i+1]的答案
现在有空位和m个数可用,每个数只能用一次。
其实每个数只能用一次这个条件并没有什么用,因为这是严格的LIS,用两次也没有用
所以可以先去重。
然后O(n+m)地枚举去更新low数组,因为空位数k<=1000,所以这是可以过的。
至于输出方案,记录
pre[i]i结尾的最长LIS的上一个位置(空位为-1)
f[i]i结尾LIS最长长度
last[len] 长度为len的LIS最小结尾位置 (空位为-1)
倒序求方案
设当前位置为x
如果pre[x]!=-1,即上一位不是空位,直接x=pre[x]
否则枚举前面不是空位的一个位置y,填充好f[y]-f[x]个空位再令x=y即可
什么样的y可能在LIS中呢?
只要满足f[x]-f[y]=min(gap(x,y),num(x,y))即可
gap(l,r)为(l,r)间的空位数num(x,y)为m个数中有多少个可以填在a[x],a[y]之间
填完LIS后,无关紧要的空位随便填没有用过的数即可。
#include<cstdio> #include<cstring> #include<algorithm> const int maxn=100010,inf=1e9; using namespace std; int n,m,a[maxn],b[maxn],low[maxn],pre[maxn],f[maxn],last[maxn],gap[maxn]; //low[len]长度为len的LIS最小结尾 //pre[i]i结尾的最长LIS的上一个位置,输方案要用 //f[i]i结尾LIS最长长度 //last[len] 长度为len的LIS最小结尾位置 void work(){ memset(low,63,sizeof(low)); pre[1]=0,f[1]=1,last[1]=1,low[1]=0; for (int k=2;k<=n;k++){ if (a[k]!=-1){//有数 int l=1,r=n,mid=(l+r)>>1; while (l<=r){ if (low[mid]<a[k]) l=mid+1; else r=mid-1; mid=(l+r)>>1; } pre[k]=last[l-1],last[l]=k; f[k]=l,low[l]=a[k]; } else{//gap pre[k]=-1,f[k]=-1; for (int i=m,j=n;i>=1;i--){//从大到小枚举可填的数 while (low[j]>=b[i]) j--;//low有单调性,所以不用二分。 low[j+1]=b[i],last[j+1]=-1; } } } // for (int i=n;i;i--)if (low[i]>0&&low[i]!=1061109567) {printf("%d %d ",i,low[i]);break;} } void print(){//倒序求方案 int l,r,rest=m;//l,r表示b[l]-b[r]的数可以填在a[x]和a[y]之间 ,rest表示可用的数还有哪些 for (int x=n;x>=1;) if (a[x]!=-1){ if (pre[x]>=0){x=pre[x];continue;}//LIS上一位有数,跳到上一位 r=lower_bound(b+1,b+1+rest,a[x])-b-1; for (int y=x-1;y;y--)//否则枚举上一个不是空位的位置 if (a[y]!=-1&&a[y]<a[x]){ l=upper_bound(b+1,b+1+rest,a[y])-b; if (f[x]-f[y]-1==min(r-l+1,gap[x]-gap[y])){//如果满足,位置y就在一种LIS中 for (int i=y,j=l;i<=x&&j<=r;i++) if (a[i]==-1) a[i]=b[j],b[j]=-1,j++;//填充y到x这段区间 x=y,rest=l-1;break;//继续向前做 } } } for (int i=1,j=1;i<=n;i++){ if (a[i]==-1){ for (;b[j]==-1;j++); a[i]=b[j],b[j++]=-1; } } for (int i=2;i<n;i++) printf("%d ",a[i]);puts(""); } int main(){ scanf("%d",&n),gap[1]=gap[0]=0; for (int i=2;i<=n+1;i++) scanf("%d",&a[i]),gap[i]=gap[i-1]+(a[i]==-1); a[1]=0,a[n+2]=inf,gap[n+2]=gap[n+1],n+=2; scanf("%d",&m);for (int i=1;i<=m;i++) scanf("%d",&b[i]); sort(b+1,b+1+m),m=unique(b+1,b+1+m)-b-1;//LIS是严格上升的,所以每个数最多用一次 work(),print(); return 0; } /* 100 -1 2 3 74 90 39 37 18 23 -1 5 -1 56 88 99 49 72 11 19 6 81 24 8 23 64 -1 100 77 61 87 23 -1 20 15 -1 55 25 40 4 25 73 85 87 72 5 98 46 49 -1 67 81 58 3 -1 22 14 -1 -1 92 -1 78 53 64 23 84 10 -1 54 83 55 24 -1 79 23 92 41 -1 -1 93 -1 59 90 64 93 95 22 -1 67 -1 33 41 84 37 73 -1 -1 18 49 50 58 20 57 23 22 93 77 19 85 32 79 94 20 59 78 35 16 92 33 94 42 11 */