最长单增子序列的打印

来源:互联网 发布:新浪微博程序员 编辑:程序博客网 时间:2024/06/05 07:27

转载自:传送门

题目:

求一个一维数组arr[n]中的最长递增子序列的长度,如在序列1,5,8,3,6,7中,最长递增子序列长度为4 (即1,3,6,7)。


由于LIS用O(NlogN)也能打印,O(N^2)的DP方法见最后。


从LIS的性质出发,要想得到一个更长的上升序列,该序列前面的数必须尽量的小

对于原序列1,5,8,3,6,7来说,当子序列为1,5,8时,遇到3时,序列已经不能继续变长了。但是,我们可以通过替换,使“整个序列看上去更小,从而有更大的机会去变长。这样,当替换5-3和替换8-6完成后(此时序列为1,3,6),我们可以在序列末尾添加一个7了。

那为什么复杂度可以是O(NlogN)呢?

关键就在“替换”这一步上,若直接遍历序列替换,每次替换都要O(N)的时间。但是只要我们再次利用LIS的性质——序列是有序的(单调的),就可以用二分查找,在O(logN)的时间内完成一次替换,所以算法的复杂度是O(NlogN)的。

代码如下:

[cpp] view plain copy
  1. #include<bits/stdc++.h>  
  2. using namespace std;  
  3.   
  4. const int inf = 0x3f3f3f3f;  
  5. const int mx = int(1e5) + 5;  
  6.   
  7. int a[mx], dp[mx], pos[mx], fa[mx];  
  8. vector<int> ans;  
  9.   
  10. int get_lis(int n)  
  11. {  
  12.     memset(dp, 0x3f, sizeof(dp));  
  13.     pos[0] = -1;  
  14.     int i, lpos;  
  15.     for (i = 0; i < n; ++i)  
  16.     {  
  17.         dp[lpos = (lower_bound(dp, dp + n, a[i]) - dp)] = a[i];  
  18.         pos[lpos] = i; /// *靠后打印  
  19.         fa[i] = (lpos ? pos[lpos - 1] : -1);  
  20.     }  
  21.     n = lower_bound(dp, dp + n, inf) - dp;  
  22.     for (i = pos[n - 1]; ~fa[i]; i = fa[i]) ans.push_back(a[i]);  
  23.     ans.push_back(a[i]); /// 最后逆序打印ans即可  
  24.     return n;  
  25. }  


例题:

POJ 3903 Stock Exchange

UVA 481 What Goes Up

推广:带权值的最长上升子序列:

UVa 11790 Murcia's Skyline

HDU 1087 Super Jumping! Jumping! Jumping!


另:最长不降子序列:

[cpp] view plain copy
  1. #include<bits/stdc++.h>  
  2. using namespace std;  
  3. const int mx = 10005;  
  4.   
  5. int lis[mx];  
  6.   
  7. bool cmp(int a, int b)  
  8. {  
  9.     return a <= b;  
  10. }  
  11.   
  12. int main()  
  13. {  
  14.     int N, len, i, j, x;  
  15.     while (~scanf("%d", &N))  
  16.     {  
  17.         len = 0;  
  18.         for (i = 1; i <= N; ++i)  
  19.         {  
  20.             scanf("%d", &x);  
  21.             j = lower_bound(lis + 1, lis + len + 1, x, cmp) - lis;  
  22.             lis[j] = x;  
  23.             len = max(len, j);  
  24.         }  
  25.         printf("%d\n", len);  
  26.     }  
  27.     return 0;  
  28. }  

最长递减子序列:

[cpp] view plain copy
  1. #include<bits/stdc++.h>  
  2. using namespace std;  
  3. const int mx = 10005;  
  4.   
  5. int lis[mx];  
  6.   
  7. int main()  
  8. {  
  9.     int N, len, i, j, x;  
  10.     while (~scanf("%d", &N))  
  11.     {  
  12.         len = 0;  
  13.         for (i = 1; i <= N; ++i)  
  14.         {  
  15.             scanf("%d", &x);  
  16.             j = lower_bound(lis + 1, lis + len + 1, x, greater<int>()) - lis;  
  17.             lis[j] = x;  
  18.             len = max(len, j);  
  19.         }  
  20.         printf("%d\n", len);  
  21.     }  
  22.     return 0;  
  23. }  

附:O(N^2)算法

像LCS一样,从后向前分析,很容易想到,第i个元素之前的最长递增子序列的长度要么是1(单独成一个序列),要么就是第i-1个元素之前的最长递增子序列加1,这样得到状态方程:

        LIS[i] = max{1,LIS[k]+1}  (∀k<i,arr[i] > arr[k])

这样arr[i]才能在arr[k]的基础上构成一个新的递增子序列。

代码如下:在计算好LIS长度之后,递归输出其中的一个最长递增子序列。

[cpp] view plain copy
  1. #include<cstdio>  
  2. #include<algorithm>  
  3. using namespace std;  
  4.   
  5. int dp[31]; /* dp[i]记录到[0,i]数组的LIS */  
  6. int lis = 1;    /* LIS长度,初始化为1 */  
  7.   
  8. int LIS(int *arr, int arrsize)  
  9. {  
  10.     for (int i = 0; i < arrsize; ++i)  
  11.     {  
  12.         dp[i] = 1;  
  13.         for (int j = 0; j < i; ++j) /// 注意i只遍历比它小的元素  
  14.             if (arr[j] < arr[i])  
  15.                 dp[i] = max(dp[i], dp[j] + 1);  
  16.         lis = max(lis, dp[i]);  
  17.     }  
  18.     return lis;  
  19. }  
  20.   
  21. /* 递归输出LIS,因为数组dp还充当了“标记”作用 */  
  22. void outputLIS(int *arr, int index)  
  23. {  
  24.     bool isLIS = false;  
  25.     if (index < 0 || lis == 0)  
  26.         return;  
  27.     if (dp[index] == lis)  
  28.     {  
  29.         --lis;  
  30.         isLIS = true;  
  31.     }  
  32.     outputLIS(arr, --index);  
  33.     if (isLIS)  
  34.         printf("%d ", arr[index + 1]);  
  35. }  
  36.   
  37. int main(void)  
  38. {  
  39.     int arr[] = {1, 5, 8, 3, 6, 7};  
  40.     printf("%d\n", LIS(arr, sizeof(arr) / sizeof(*arr)));  
  41.     outputLIS(arr, sizeof(arr) / sizeof(*arr) - 1);  
  42.     return 0;  
  43. }

原创粉丝点击