codeforces568E.Longest Increasing Subsequence

传送门:http://codeforces.com/problemset/problem/568/E

思路:首先没有空位,我们是记录一个low数组表示长度为i的上升子序列的最小结尾。

对于一个末尾新的数x,我们只要二分出一个位置low[i]

现在有空位和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
#include
#include
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]>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\n",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]


转载于:https://www.cnblogs.com/thythy/p/5493515.html

你可能感兴趣的:(codeforces568E.Longest Increasing Subsequence)