题目描述:
Description
给定一长度为n的数列,数列中的每个数都在1~100000之间
请在不改变原数列顺序的前提下,从中取出一定数量的整数(不需要连续),并使这些整数构成单调上升序列。
输出这类单调上升序列的最大长度。
Input
输入包括两行,第一行为n,代表数列的长度。(1 ≤ n ≤ 100000)
第二行为n个整数。
Output
输出这类单调上升序列的最大长度
Sample Input
5
3 1 5 4 6
Sample Output
3
More Info
对于样例的解释:356,156,146 均可组成长度为3的上升子序列
一、问题分析
1.找子问题:
“求序列的前n个元素的最长上升子序列”是一个子问题,但这个方法是行不通的,因为其不具有无后效性:
设F(n)=x (前n个元素的最长上升子序列为x),但在前n个元素中,可能有多个上升子序列的值为x(结尾元素不一定是同一个,如2 3 7 6 5 6 2,当n=5时,237、236 、235)。有的序列的最后一个元素比第n+1个元素小,则能形成一个更长的子序列;有的序列的最后一个元素大于等于第n+1个元素,无法形成更长上升子序列。则当走到第n+1时,F(n+1)的值不仅和F(n)有关,还和前n个元素中的多个最长上升子序列的最后一个元素有关,和以前所了解的“只和上一个状态的值有关,与是如何到达上一个状态的方式无关”相矛盾。换句话说,F(n+1)的值应当只与F(n)的值有关。
换一个方向考虑,把子问题改成“求以a[k] (1<=k<=N)为终点的最长上升子序列的长度”,把所有的F(n) (以第n个元素结尾的最长上升子序列长度)都求出来后,只需要找出其中最大的就是答案。
2.确定状态:
如上文所说,"状态"就是以a[k] (1<=k<=N)为终点的最长上升子序列。
3.确定初始状态的值:
显然,F(1)=1;但是此处应当把所有的F初始值都设为1;不然轮到一个k,前1~k-1个元素没有比其小的,当在求后面的状态的值时,轮到k时其F(k)应当为1;
4.确定状态转移方程:
dp[i]=max(dp[i],dp[j]+1);
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int a[100005],dp[100005];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i]=1;
}
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
dp[i]=max(dp[i],dp[j]+1);
}
}
cout<<*max_element(dp+1,dp+n+1);
}
二、改进
上述代码提交后你就会发现TLE了-_-||(因为时间复杂度为n^2) .
那么如何改呢。这里用了非DP方法,通过辅助数组s[],结合数列本身特性求解,s[]的长度即为答案。
操作:逐个处理a[]里面的数字(1)如果a[i]比辅助数组s[]的最后一个数更大,s[]长度加一,把a[i]添加到s[]结尾;(2)如果a[i]比s[]最后一个数小,就用lower_bound()二分查找s[]中第一个大于等于a[i]的位置,替换之。
初始化:s[1]=a[1];tot=1;
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,a[100010];
int s[100010],tot=0,tmp;
int main(){
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
s[++tot]=a[1];
for(int i=2;i<=n;i++){
if(a[i]>s[tot])s[++tot]=a[i];
else{
tmp=lower_bound(s+1,s+tot+1,a[i])-s;//STL的常用算法,二分查找s中第一个大于等于a[i]的数,用a[i]替换,复杂度为O(logn)(s[]必须有序)
s[tmp]=a[i];
}
}
printf("%d\n",tot);
return 0;
}
解释:
为什么这样能行呢?对于操作(1),如果a[i]比辅助数组s[]的最后一个数更大,s[]长度加一,把a[i]添加到s[]结尾,很好理解。(2)"如果a[i]比s[]最后一个数小,就用lower_bound()二分查找s[]中第一个大于等于a[i]的位置,替换之。"首先,这个操作并不影响s[]的长度,即LIS的长度;其次,对于a[]中后面未处理的数字,可能很多都比s[]的最后一位小,但是有可能序列更长,这里的替换就给后面的更小的数字留下了机会,就不会错过形成更长LIS的可能。