最长单增子序列
(LIS Longest Increasing Subsequence)给定一个数列,从中删掉任意若干项剩余的序列叫做它的一个子序列,求它的最长的子序列,满足子序列中的元素是单调递增的。
输入
第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
请选取你熟悉的语言,并在下面的代码框中完成你的程序,注意数据范围,最终结果会造成Int32溢出,这样会输出错误的答案。
不同语言如何处理输入输出,请查看下面的语言说明。
【分析】
我们现在简单讲一下一个O(nlogn)的算法。我们假象一下dp[i][j]表示前i项时构成长度为j的单调子序列的话,最后一项最小的时候是多少。
如果没有长度为j的单调子序列,则设置为+∞。
我们证明dp[i], 随着j的增长单调递增(不考虑无穷大的时候)
初值dp[0][0] = -∞表示长度为0的单调子序列可以达到无穷小。显然dp[0]只有一项值,它是单调递增的。假设dp[i – 1]是单调递增的:
即 dp[i – 1][0] < dp[i – 1][1] < dp[i – 1][2] < dp[i – 1][3] <..<dp[i – 1][x]
其实我们可以加一项dp[i – 1][x + 1] = +∞
所以 :
dp[i – 1][0] < dp[i – 1][1] < dp[i – 1][2] < dp[i – 1][3] <..<dp[i – 1][x] < dp[i – 1][x + 1]
我
们考虑a[i]这一项有什么用。我们需要找到dp[i – 1][y] < a[i]把它接到长度为y的子序列后面,形成一个长度为y +
1的子序列。如果dp[i – 1][y + 1] < a[i], 这说明不属于a[i]这一项,我们考虑前个数也可以形成长度为y +
1的单增子序列,且最后一项更小,所以我们不应该更新它。事实上我们需要找到dp[i – 1][y] < a[i] && dp[i – 1][y + 1] >= a[i], 这样把a[i]接在长度为y的子序列后面形成一个长度为(y + 1)的子序列,同时结尾更小。
于是我们有递推关系:
dp[i][0..y] = dp[i – 1][0..y]
dp[i][y + 1] = a[i]
dp[i][y + 2..] = dp[i – 1][y + 2…]
y的存在性,由于我们添加了 -∞和+∞,我们一定能找到满足上述条件的y值。而且根据单调性,我们可以利用二分查找的方法找到这个临界的y值。注意最后找到的y有可能就等于x,然后我们更新的时候,会更新dp[x + 1] = a[i],这样子序列的长度增长了1。
因为每次只更新一个值,我们dp数组只存第二维就可以了。最终的结果,其实是max {x| dp[x] < +∞}的x。
时间复杂度,二分是O(logn),所以总时间复杂度是O(nlogn)。
于是我们有递推关系:
dp[i][0..y] = dp[i – 1][0..y]
dp[i][y + 1] = a[i]
dp[i][y + 2..] = dp[i – 1][y + 2…]
实际上我就更新了一个值,而更新的这个值的递推式,也同时证明了这个序列的单调性。
y的存在性,由于我们添加了 -∞和+∞,我们一定能找到满足上述条件的y值。而且根据单调性,我们可以利用二分查找的方法找到这个临界的y值。注意最后找到的y有可能就等于x,然后我们更新的时候,会更新dp[x + 1] = a[i],这样子序列的长度增长了1。
因为每次只更新一个值,我们dp数组只存第二维就可以了。最终的结果,其实是max {x| dp[x] < +∞}的x。
时间复杂度,二分是O(logn),所以总时间复杂度是O(nlogn)。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <cmath> #include <time.h> #include <string> #include <stack> #include <vector> #include <set> #include <queue> using namespace std; int n,a[50005],d[50005],i,j,len; int binsearch(int x) { int l = 1, r = len, mid; while (l <= r) { mid = (l + r) >> 1; if (d[mid-1] <= x && x < d[mid]) return mid; else if (x > d[mid]) l = mid + 1; else r = mid - 1; } } int main() { scanf ("%d", &n); for (i = 1; i<= n; i++) scanf ("%d", &a[i]); memset (d, 0, sizeof (d)); d[1] = a[1]; len = 1; for (i = 2; i <= n; i++) { if (a[i] < d[1]) j = 1; else if (a[i] > d[len]) j = ++len; else j = binsearch (a[i]); d[j] = a[i]; } printf ("%d ", len); return 0; }
下面的代码只能过四分之一的数据,因为超时。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <cmath> #include <time.h> #include <string> #include <stack> #include <vector> #include <set> #include <queue> using namespace std; int main() { int n,len,i,j,dp[50001]; int a[50005]; cin>>n; for(i=0; i<n; i++)cin>>a[i]; int maxn=1;dp[0]=1; for(int i=1; i<n; i++) { dp[i]=1; for(int j=0; j<i; j++) { if(a[i]>a[j]) { dp[i]=max(dp[i],dp[j]+1); } } maxn=max(maxn,dp[i]); } cout<<maxn<<endl; return 0; }