给定一个数列,从中删掉任意若干项剩余的序列叫做它的一个子序列,求它的最长的子序列,满足子序列中的元素是单调递增的
例如给定序列{1,6,3,5,4},答案是3,因为{1,3,4}和{1,3,5}就是长度最长的两个单增子序列。
处看此题,怎么做? 万能的枚举?枚举全部2^n个子序列,找出最长的,固然可以,就是复杂度太高。我们为什么要枚举呢?因为要知道取了哪些数,其实我们只需要考虑上一个数和取了几个数就可以了吧?因为单增的意思是比前一个数大,我们要加入这个数的时候,只考虑它比之前加入的最后一个数大就可以了。而最长的意思是数的个数最多,我们只要知道数的总个数就可以了,没必要知道具体有哪些数。
让我们尝试一下用动态规划的思考办法。首先设置数列是a1, a2, a3…an,为了方便我们加入一项a0=-∞,后面我们将发现这会给我们带来极大的方便。int f[i]表示以第i个数结尾的最长单调子序列的长度, 那么我们看一下加入ai之前的最后一个数是aj,显然j < i并且aj < ai,我们有f(i) = f(j) + 1,因为往后面延长了一项嘛。那根据这个式子,我们显然应该选择最大的f(j),才能让f(i)最大。
于是我们有了递推关系f(i) = max{f(j)| j < i并且aj < ai} + 1,光有了递推关系还不够,初值呢? f(0) = 0,并且我们加入了a0=-∞,这样对每个i > 0,j总是存在的,大不了就达到下标0了嘛。

例如给定序列{1,6,3,5,4},答案是3,因为{1,3,4}和{1,3,5}就是长度最长的两个单增子序列。
处看此题,怎么做? 万能的枚举?枚举全部2^n个子序列,找出最长的,固然可以,就是复杂度太高。我们为什么要枚举呢?因为要知道取了哪些数,其实我们只需要考虑上一个数和取了几个数就可以了吧?因为单增的意思是比前一个数大,我们要加入这个数的时候,只考虑它比之前加入的最后一个数大就可以了。而最长的意思是数的个数最多,我们只要知道数的总个数就可以了,没必要知道具体有哪些数。
让我们尝试一下用动态规划的思考办法。首先设置数列是a1, a2, a3…an,为了方便我们加入一项a0=-∞,后面我们将发现这会给我们带来极大的方便。int f[i]表示以第i个数结尾的最长单调子序列的长度, 那么我们看一下加入ai之前的最后一个数是aj,显然j < i并且aj < ai,我们有f(i) = f(j) + 1,因为往后面延长了一项嘛。那根据这个式子,我们显然应该选择最大的f(j),才能让f(i)最大。
于是我们有了递推关系f(i) = max{f(j)| j < i并且aj < ai} + 1,光有了递推关系还不够,初值呢? f(0) = 0,并且我们加入了a0=-∞,这样对每个i > 0,j总是存在的,大不了就达到下标0了嘛。

伪代码:
f[0] = 0;
for i = 1 to n do
f[i] = 0;
for j = 0 to i – 1 do
f[i] = max(f[i], f[j] + 1)
endfor
endfor
显然这个算法的时间复杂度是O(n^2),空间复杂度是O(n)。老生常谈的问题,如何找到这样一个最长的子序列?记录决策的办法总是可以的我们记录一下使得f(i)最大的j。最终结果是max{f(i)},我们从这个i值一项一项不断找到前面的j即可……
更好的算法?
事实上这个题有时间复杂度更低的算法。仍然以{1,6,3,5,4}为例子,我们想像考虑5的时候,之前有两个长度为2的子序列{1,6}和{1,3},那么哪个更“好”呢?显然后者更好,因为3比6小,以3结尾的序列更容易在后面接上一个数。那么我们记录到第i个数之前每个长度的单调子序列中“最好”的那个的最后一个数的大小,考虑把当前这个数接在哪里就好了。事实上,我们的意思是在每个长度的单调子序列中选一个代表,这个代表就是其中“最好”的那个(让最后一项尽可能小),而我们可以归纳的证明,不同长度的“最好”单调子序列的最后一项是随着长度而单调递增的。这是因为,我们每次都试图把一个数加到它能接的那个最长的子序列后面,其实也是加到了它能加的末尾最大的子序列上。
那么问题明了了,开始我们只有一个长度为0的单调子序列,末尾大小认为是-∞。假设目前我们记录了f[0],f[1],f[2]…f[m]表示目前单调子序列的最长长度是m,我们考虑ai接到哪里,我们要找到小于ai的最大那一项,我们把它接到那个序列的后面。因为f是单调递增的,换句话说,我们找到x= max{x|f[x] < ai}, 把ai接到f[x]后面,得到f[x + 1] = ai,注意这样的x一定存在,因为f[0] = -∞。如果我们找到的x < m,则我们实际上更新了长度为(x + 1)的子序列的最后一项,因为显然有f[x + 1] >= ai,我们把ai换过去,至少不会变差,这也正式我们保存每个长度“最好”的单调子序列的初衷。如果x == m,则实际上我们把子序列的长度(种类数)扩展到了(m + 1)。
最终结果是什么呢?是f那个列表的长度,也就是最终变化后的m值。
如果我们循环一个一个地看,这里就有O(m)的时间复杂度,但是因为有单调性的存在,我们可以利用二分查找算法来找到这样的x,所以这里时间复杂度是O(logm),因为m<=n,我们这里可以认为每次找到x的时间复杂度是O(logn),那么对于每个ai我们都如此做的时间复杂度就是O(nlogn)了。
我们得到了一个更快的算法。请思考如何找到具体一个子序列?(提示:仍然是“记录”决策,在找到x的时候记录就可以了。)
事实上这个题有时间复杂度更低的算法。仍然以{1,6,3,5,4}为例子,我们想像考虑5的时候,之前有两个长度为2的子序列{1,6}和{1,3},那么哪个更“好”呢?显然后者更好,因为3比6小,以3结尾的序列更容易在后面接上一个数。那么我们记录到第i个数之前每个长度的单调子序列中“最好”的那个的最后一个数的大小,考虑把当前这个数接在哪里就好了。事实上,我们的意思是在每个长度的单调子序列中选一个代表,这个代表就是其中“最好”的那个(让最后一项尽可能小),而我们可以归纳的证明,不同长度的“最好”单调子序列的最后一项是随着长度而单调递增的。这是因为,我们每次都试图把一个数加到它能接的那个最长的子序列后面,其实也是加到了它能加的末尾最大的子序列上。
那么问题明了了,开始我们只有一个长度为0的单调子序列,末尾大小认为是-∞。假设目前我们记录了f[0],f[1],f[2]…f[m]表示目前单调子序列的最长长度是m,我们考虑ai接到哪里,我们要找到小于ai的最大那一项,我们把它接到那个序列的后面。因为f是单调递增的,换句话说,我们找到x= max{x|f[x] < ai}, 把ai接到f[x]后面,得到f[x + 1] = ai,注意这样的x一定存在,因为f[0] = -∞。如果我们找到的x < m,则我们实际上更新了长度为(x + 1)的子序列的最后一项,因为显然有f[x + 1] >= ai,我们把ai换过去,至少不会变差,这也正式我们保存每个长度“最好”的单调子序列的初衷。如果x == m,则实际上我们把子序列的长度(种类数)扩展到了(m + 1)。
最终结果是什么呢?是f那个列表的长度,也就是最终变化后的m值。
如果我们循环一个一个地看,这里就有O(m)的时间复杂度,但是因为有单调性的存在,我们可以利用二分查找算法来找到这样的x,所以这里时间复杂度是O(logm),因为m<=n,我们这里可以认为每次找到x的时间复杂度是O(logn),那么对于每个ai我们都如此做的时间复杂度就是O(nlogn)了。
我们得到了一个更快的算法。请思考如何找到具体一个子序列?(提示:仍然是“记录”决策,在找到x的时候记录就可以了。)
最后,我们来提供输入输出数据,由你来写一段程序,实现这个算法,只有写出了正确的程序,才能继续后面的课程。
输出示例
输入
第1行:1个数N,N为序列的长度(2 <= N <= 50000) 第2 - N + 1行:每行1个数,对应序列的元素(-10^9 <= S[i] <= 10^9)
输出
输出最长递增子序列的长度。
输入示例
8 5 1 6 8 2 4 5 10
输出示例
5
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include <set>
#include <map>
#include<list>
#include <stack>
#include <queue>
#include <vector>
#include <ctime>
#define ll long long
#define f(i,a,b) for(int i=a;i<=b;i++)
#define m(a,b) memset(a,b,sizeof(a))
#define MAX 0x3f3f3f3f
const ll MOD=1e+9+7;
using namespace std;
int BSearch(int a[], int n, int t)
{
int low = 1;
int high = n;
while (low <= high)
{
int mid = (low + high) / 2;
if (t == a[mid])
{
return mid;
}
else if (t > a[mid])
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}
return low;
}
int LIS_BSearch(int a[], int m[], int n)
{
int maxlen = 1; //最长上升子序列的长度
m[maxlen] = a[1];
int i;
for (i = 2; i <= n; i++)
{
if (a[i] > m[maxlen])
{
m[++maxlen] = a[i];
}
else
{
//返回小于a[i]的最大值的位置p
int p = BSearch(m, maxlen, a[i]);
m[p] = a[i];
}
}
return maxlen;
}
int main()
{
int s[50005],ans[50005];
int n;
int maxx=0;
m(s,0);
cin>>n;
//O(nlogn)
m(ans,0);
f(i,1,n)cin>>s[i];
maxx=LIS_BSearch(s,ans,n);
/*
//O(n^2)
f(i,0,50003)ans[i]=1;
cin>>s[0];
f(i,1,n-1){
cin>>s[i];
max=1;
for(int j=i-1;j>=0;j--){
if(s[i]>s[j]&&ans[i]+ans[j]>max&&ans[j]){
max=ans[i]+ans[j];
ans[j]=0;
}
}
ans[i]=max;
if(max>maxx)maxx=max;
}
*/
cout<<maxx<<endl;
return 0;
}