题目描述
给定数组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;
}