CD 25 最长递增子序列

题目描述
给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)
输入描述:
输出两行,第一行包括一个正整数n(n<=100000),代表数组长度。第二行包括n个整数,代表数组arr \left(1 \leq arr_i \leq 1e9 \right)(1≤arr
i

≤1e9)。
输出描述:
输出一行。代表你求出的最长的递增子序列。
示例1
输入
复制
9
2 1 5 3 6 4 8 9 7
输出
复制
1 3 4 8 9
示例2
输入
复制
5
1 2 8 6 4
输出
复制
1 2 4
说明
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个字典序最小,故答案为(1,2,4)
备注:
时间复杂度O(nlogn),空间复杂度O(n)。

 #include<iostream>
 #include<algorithm>
 using namespace std;
 
  	int a[100007];
 	int b[100007];//end数组表示遍历到当前位置,下标为i,储存的是 长度为 i+1 的最小结尾数 
 	int dp[100007];
 	int res=1,index=0;//子序列的最长长度,和最后一位的下标 
 int main()
 {

 	int n;
 	scanf("%d",&n);
 	for(int i=0;i<n;i++)
 	  scanf("%d",&a[i]);
 	
 	b[0]=a[0];
	dp[0]=1;
	int right=0,l=0,r=0,m=0;//right表示end数组的最后一位数的下标
	
	for(int i=1;i<n;i++)
	{
		l=0,r=right;
		while(l<=r)//寻找到a[i]该放在end[] 那个位置(a[i],放在end数组中第一个大于a[i]的位置上,如果没有就放在最后) 
		{
			m=(l+r)/2;
			if(a[i]>b[m])
			{
				l=m+1;
			}
			else
			{
				r=m-1;
			}
		}
		right=max(l,right);
		b[l]=a[i];//此时l表示a[i]应该在end数组中那个位置 
		dp[i]=l+1;
		if(dp[i]>res || (dp[i]==res&& a[index]>a[i] ) )//保证index储存的下标是最长子序列的,字典序最小的下标 
		{
			res=dp[i];
			index=i;
		}
	} 
//	cout<<res<<endl; //最长的长度 
	int ans[res+1] ;//结果
	int len=res;
	ans[res--]=a[index]; 
	for(int i=index;i>=0;i--)
	{
		if(a[i]<a[index] && dp[index]-dp[i]==1 )
		{
			ans[res--]=a[i];
			index=i;
		}
	}
	for(int i=1;i<=len;i++)
	  cout<<ans[i]<<" ";
 
 	return 0;
 } 

上面二分法可以改写为lower_bound

 #include<iostream>
 #include<algorithm>
 using namespace std;
 
  	int a[100007];
 	int b[100007];//end数组表示遍历到当前位置,下标为i,储存的是 长度为 i+1 的最小结尾数 
 	int dp[100007];
 	int res=1,index=0;//子序列的最长长度,和最后一位的下标 
 int main()
 {

 	int n;
 	scanf("%d",&n);
 	for(int i=0;i<n;i++)
 	  scanf("%d",&a[i]);
 	
 	b[0]=a[0];
	dp[0]=1;
	int right=0,l=0,r=0,m=0;//right表示end数组最后一位的下标 
	
	for(int i=1;i<n;i++)
	{
		l=0,r=right;
		int k= lower_bound(b,b+right+1,a[i]) - b ;//a[i]应该放在k位置  
	//	cout<<a[i]<<"位置:k: "<<k<<endl;
		right=max(k,right);
		b[k]=a[i];//此时l表示a[i]应该在end数组中那个位置 
		dp[i]=k+1;
		if(dp[i]>res || (dp[i]==res&& a[index]>a[i] ) )//保证index储存的下标是最长子序列的,字典序最小的下标 
		{
			res=dp[i];
			index=i;
		}
	} 
	
 
 
	int ans[res+1] ;//结果
	int len=res;
	ans[res--]=a[index]; 
	for(int i=index;i>=0;i--)
	{
		if(a[i]<a[index] && dp[index]-dp[i]==1 )
		{
			ans[res--]=a[i];
			index=i;
		}
	}
	for(int i=1;i<=len;i++)
	  cout<<ans[i]<<" ";
 
 	return 0;
 } 

更加简介,增加证明


#include<iostream>
#include<string> 
#include<algorithm>
using namespace std;

 int a[100007];
 int b[100007];//end数组,便利数组a[]每次插入到其中,位置+1== 以a[i]下标结尾的最长子序列长度 
 int dp[100007];//在这里dp数组用来寻找最长子序列的路径 
int main()
{
	int n,len=0,res=-1,index=0;//len表示b数组长度 
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
		int k= lower_bound(b,b+len,a[i])-b;
		b[k]=a[i];
	    dp[i]=k+1;
	    //更改最长子序列结束路径。 
		//证明: dp[x]==dp[y],x<y, 若a[x]<a[y],则a[y]可以放在a[x]后面,dp[x]<dp[y](假设不成立)。所以出现 res==dp[i] 的时候总是可以更新路径。 
	    if(res<=dp[i])
		{
			res=dp[i];
			index=i;
		}
		if(k+1>len)
		   len=k+1;
	}
	int ans[res+1];
	int kk=res;
	ans[res--]=a[index];
	for(int i=index-1;i>=0;i--)
	{
		if( dp[index]-dp[i]==1 )//由上面证明得来。 
		{
			ans[res--]=a[i];
			index=i;
		}
	}
	for(int i=1;i<=kk;i++)
	  cout<<ans[i]<<" " ; 
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值