zoukankan      html  css  js  c++  java
  • 题解 CF568E 【Longest Increasing Subsequence】

    CF通道

    luogu链接


    大致题意

    给你一个长度为n的序列,这其中可能有-1,代表着空位

    然后给你m个数,代表空位可选的m个数

    然后叫你补全序列(每个(b[i])只能用一遍),是补全后得到的序列LIS(严格)最大

    输入格式:

    (n)

    (a_1;a_2;a_3cdots;a_n)

    (m)

    (b_1;b_2;b_3cdots;b_m)


    思路分析

    因为这道题是叫我们求LIS的构造方案,我们(理所应当)应该想到LIS的求的方法。

    1. 方法1:循环枚举法((n^2)

    这个方法是利用双重(for)循环,然后进行LIS的判断

    //LIS[i]代表的是以i为终点的LIS的最大长度
    for(register int i=1;i<=n;i++)
    	for(register int j=0;j<i;j++)
        	if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1);
    
    1. 方法2:利用辅助数组(f[i])进行LIS((n imes logn))

    (f[x])代表的是长度为x的LIS的末尾项最小是多少,而我们每一次遍历到(i)时,我们都可以进行二分查找,找到小于(a[i])的最大的f[x],然后赋值(f[x+1]=a[i])

    //f[i]意义同上
    //ans代表的是最长上升子序列
    memset(f,0x3f3f3f3f3f,sizeof(f));
    for(register int i=1;i<=n;i++){
    	int j=lower_bound(f+1,f+1+n,a[i])-f-1;
        f[j+1]=a[i];
        ans=max(ans,j+1);
    }
    

    从复杂度讲

    [n leq 10^5 ]

    还是从是否有空位讲

    我们都应该选择从第二个方法入手。

    显而易见,我们应该把空位和数字分开处理。

    • 数字

    对于数字来说,我们就按照上面的LIS一样处理就好了

    • 空位

    对于空位来说,我们应该在这个位置上枚举每一个(b[i]),然后分别像数字一样用LIS。


    感觉上是不是弄好了,但是有一点值得注意的是,答案不是叫我们输出可能得到的最大LIS的长度,而是叫我们输出修改后的序列,使这个序列里面有可能长度最大的LIS,所以我们还有两个问题亟需解决。

    1. 题目中要求每个(b[i])只能用一遍

    2. 如何输出我们求到的序列


    (color{red}{Solution :})

    1. 关于只能用一遍

    因为如果我们在两个不同的空位用上相同的位置,这两个位置之间没有办法做出贡献(因为要求的是严格),所以即使我们这两个点都是相同的,那么对于前面的序列,贡献都一样,而互相没有贡献,所以这个限制我们可以忽略。

    1. 关于输出

    我们要输出这个序列,如果是正序枚举然后挨个挨个向后推的话显然是没法的。

    原因:

    因为你用LIS求出来的永远都只是以这个点为终点可以构造出来的最长LIS,所以你遍历到这个点可以保证在这个点之前可以构造LIS,可是如果你从前往后遍历的话,你根本不知道你现在构造出来的序列是不是你要求的最终序列,所以只能从后往前构造,而不能从前往后构造。


    所以我们选择从后往前构造,现在问题又来了,请问你遍历到一个点后,你打算跳到哪个点呢?

    这时候我们应该增加两个辅助数组,一个是(g[i])代表的是以i为结束点的LIS长度是多少,一个(h[i])代表的是以i为结束点的LIS的上一个点是多少。

    而且我们必须保证i不为空位

    为什么呢?

    因为我们对于空位的处理方法是枚举这m个数,然后挨个挨个去更新,那么最坏情况下这个位置上有m个不同的LIS,那么违反了我们两个数组的定义,所以我们不能算上空格。

    所以:

    1. 位置不是空位

    (h[i])就好了

    1. 位置是空位

    那么就先找有没有不是空位的数可以构造出剩下的LIS,不然的话就用最近的空格。

    (因为我们保证了每个数我们都清楚以他为终点的LIS的最大长度,所以我们可以先找确定的点,这样的话我们就省下了一个填补的数,而如果非得用空格的话,那就选最近的空格,这也是一个贪心的思想)


    基于上面的思想,我们就可以打代码了(具体细节还得看代码)

    [color{blue}{Talk;is;cheap,show;the;code;!} ]

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #define ri register int
    #define Starsseven main
    using namespace std;
    const int N=1e5+20;
    const int inf=999999999;
    int a[N],b[N];
    
    inline int read() {
        int X=0,w=1; char c=getchar();
        while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
        while (c>='0'&&c<='9') X=X*10+c-'0',c=getchar();
        return X*w;
    }
    
    int n,m,ans[N];
    int Lis_minn[N]/*这个数组[i]表示的是LIS长为i时结尾最小的数*/,Lis_this[N]/*表示的是以i结尾(i不是空位)的LIS长度*/;
    int Lis_last[N]/*这个数组表示的是长度为i的LIS的结尾最小值位置在哪里*/,Lis_before[N]/*表的是以i(i不是空位)为结尾的LIS的上一项*/;
     
    
    bool vis[N];
    void Find(int i,int h,int &x){
    	int j=lower_bound(b+1,b+1+m,h)-b-1;
    	vis[j]=1;x=ans[i]=b[j];
    }
    //Find(Lis_before[j],a[j],x)
    int main(void){
    	n=read();//表示有n个数构成的序列 
    	for(ri i=1;i<=n;i++) a[i]=read();//读入a[i] 
    	n++;a[n]=inf;
    	m=read();//表示有m个可选的数 
    	for(ri i=1;i<=m;i++) b[i]=read();//表示这m个数的大小
    	sort(b+1,b+1+m);//将b[]从小到大排序,因为我们之后要用到的是二分出最小的b[i]可以替换
    	for(ri i=1;i<=n;i++) Lis_minn[i]=inf;//先初始化minn数组 
    	//int Lis_minn[N]/*这个数组[i]表示的是LIS长为i时结尾最小的数*/,Lis_this[N]/*表示的是以i结尾(i不是空位)的LIS长度*/;
    	//int Lis_last[N]/*这个数组表示的是长度为i的LIS的结尾最小值位置在哪里*/,Lis_before[N]/*表的是以i(i不是空位)为结尾的LIS的上一项*/;
    	for(ri i=1;i<=n;i++)/*从前往后枚举a[i]*/{
    		if(a[i]!=-1){//表示这个数不是空位 
    			int j=lower_bound(Lis_minn+1,Lis_minn+1+n,a[i])-Lis_minn-1;//这个表示的是找到可以更新得到的数组
    			/*因为lower_bound求的是Lis_minn中小于a[i]的最大值,这样就可以保证:
    				在j之后的数a[i]更新不了
    				而在j之前的数,因为a[i]>Lis_minn[j],如果更新j-1则绝对不是"最小值"*/
    			Lis_minn[j+1]=a[i];Lis_this[i]=j+1;
    			Lis_last[j+1]=i; Lis_before[i]=Lis_last[j];
    		}
    		else{//表示这就是空位 
    			for(ri j=m,x=n;j;j--){
    				while(Lis_minn[x]>=b[j]) --x;//找到b[j]可以更新的长度为x-1的数,为什么不更新前面的和后面的同理同上
    				Lis_minn[x+1]=b[j];
    				Lis_last[x+1]=i;
    			}
    		}
    	}
    	//下面是输出答案
    	ri i=Lis_this[n],j=n,x=a[j];
    	while(i--){
    	//int Lis_minn[N]/*这个数组[i]表示的是LIS长为i时结尾最小的数*/,Lis_this[N]/*表示的是以i结尾(i不是空位)的LIS长度*/;
    	//int Lis_last[N]/*这个数组表示的是长度为i的LIS的结尾最小值位置在哪里*/,Lis_before[N]/*表的是以i(i不是空位)为结尾的LIS的上一项*/;
    		if(a[j]!=-1){
    			if(a[Lis_before[j]]==-1) Find(Lis_before[j],a[j],x);
    			else x=a[Lis_before[j]];
    			j=Lis_before[j];
    		}
    		else{
    			bool Judge=false;
    			for(ri s=j-1;s;s--)
    				if(a[s]!=-1&&Lis_this[s]==i&&a[s]<x){
    					x=a[j=s],Judge=1;break;
    				}
    			if(Judge) continue;	
    			for(ri s=j-1;s;s--){
    				if(a[s]==-1){
    					Find(s,x,x),j=s;
    					break;
    				}
    			}
    		}
    	}
    	for(ri i=1,j=1;i<=n;i++){
    		if(a[i]==-1){
    			if(ans[i]) continue;
    			while(vis[j]) j++;
    			vis[j]=1,ans[i]=b[j];
    		}
    		else ans[i]=a[i];
    	}
    	for(ri i=1;i<n;i++) printf("%d ",ans[i]); 
    	return 0;
    } 
    
  • 相关阅读:
    『ORACLE』 DG切换主备库角色(11g)
    Java基础语法(三)---数组
    JDK安装与配置详细图文教程
    谁把20岁上下的你给洗脑了
    看看已堕落的自己
    关于Git
    自定义UIDatePikerView
    细节关注(持续更新。。。)
    如何生成圆形的图片
    高效使用你的Xcode
  • 原文地址:https://www.cnblogs.com/starseven/p/13099381.html
Copyright © 2011-2022 走看看