题意:给出一串数字,代码高度。求其最长升序列的长度 ,
并输出该序列。如果有多组输出字典数最小的。
思路:由于这个题的数组长度很长,所以直接做的时间复杂度是O(N^2),所以会超时。
这时就要想出一个时间复杂度为O(NlogN)的方法。
用一个数组记录前i个数据的每个长度的升序列的位置及最小的数。
对于加入的每个数,搜到其应该加入序列的位置(用二分查找,加速的关键)
对于多级的情况,仔细一想就知道从末尾依次找到前,遇到的第一个最长序列的尾就是我们要找的(用反证法)。
#include<iostream>
#include<cstdio>
using namespace std;
#define N 100010
int n;
int go[N];
int len;
int m[N],at[N]; //m[i]存升序列的中的每个数的值。at[i]记录m[i]在原数列中的位置
int pre[N],pai[N]; //pre[i]存原序列中的前一个升序列所在序列中的位置。pai[i]存第i个数字在升序列中排第几。
int search(int x)
{
int r=1,l=len;
int mid=0;
if (x>m[len])
return len+1;
if (x<=m[1])
return 1;
while (r<l)
{
mid=(r+l)>>1;
//cout<<r<<' '<<mid<<' '<<l<<endl;
//cout<<m[mid]<<' '<<x<<endl;
if (m[mid] == x)
return mid;
if (m[mid] < x)
r=mid+1; //last的值总保存在r里。
else
l=mid; //这里保证[l,r]这个区间有满足要求的。
}
return r;
}
void put(int i,bool ok) //从升序列的最末找到最前,再回溯依次输出
{
if (!i)
return;
put(pre[i],false);
printf("%d",go[i]);
if (!ok) //最后一个不输出空格
printf(" ");
}
int main()
{
freopen("in","r",stdin);
int i,j,k;
int t;
cin>>t;
while (t--)
{
cin>>n;
for (i=1;i<=n;i++)
scanf("%d",go+i);
len=1;
m[1]=go[1];
at[1]=1;
pai[1]=1;
for (i=2;i<=n;i++)
{
//for (j=1;j<=len;j++)
// if (m[j]>=go[i])
// break;
j=search(go[i]);
//cout<<' '<<j<<endl;
pre[i]=at[j-1];
pai[i]=j;
if (j>len)
m[len+1]=go[i],at[len+1]=i,len++;
else
m[j]=go[i],at[j]=i;
}
for (i=n;i;i--)
if (pai[i] == len)
break;
printf("%d\n",len);
put(i,true);
printf("\n");
}
return 0;
}