九度oj 题目1533:最长上升子序列

题目描述:

给定一个整型数组, 求这个数组的最长严格递增子序列的长度。 譬如序列1 2 2 4 3 的最长严格递增子序列为1,2,4或1,2,3.他们的长度为3。

输入:

输入可能包含多个测试案例。
对于每个测试案例,输入的第一行为一个整数n(1<=n<=100000):代表将要输入的序列长度
输入的第二行包括n个整数,代表这个数组中的数字。整数均在int范围内。

输出:

对于每个测试案例,输出其最长严格递增子序列长度。

样例输入:
4
4 2 1 3
5
1 1 1 1 1
样例输出:
2
1

这个题一开始用最简单的办法来做,果然超时了
代码如下
 1 #include <cstdio>
 2 int toDo[100002];
 3 int cnt[100002];
 4 
 5 int n;
 6 int main(int argc, char const *argv[])
 7 {
 8     while(scanf("%d",&n) != EOF) {
 9         for(int i = 0; i < n ; i++) {
10           scanf("%d",&toDo[i]);
11         }
12         int max = 0;
13         for(int i = 0; i < n; i++) {
14           int maxi = 0;
15           for(int j = i-1; j >= 0; j--) {
16               if(toDo[j] < toDo[i] && maxi < cnt[j]) {
17                 maxi = cnt[j];
18               }
19           }
20           cnt[i] = maxi + 1;
21           if(max < cnt[i]) {
22             max = cnt[i];
23           }
24         }
25         printf("%d
",max);
26     }   
27     return 0;
28 }

后来参考了许多别人的博客,才明白应该这样做

首先,你需要一个数组minNum[i]来记录达到长度i的最小数字是几,这个数组必然是递增的,因为长度更长的它的数字肯定更大。

那么,当一个新数字来的时候,假设现在最长的长度为k

如果它比minNum[k]大,那么minNum[++k] = newNum;

如果不比minNum[k]大,则需要去更新这个数组中的元素。具体需采用二分法来找到这个数字的位置,接着更新之。

但一开始提交错误,原来的代码是这样的

 1 #include <cstdio>
 2 int toDo[100002];
 3 int minNum[100002];
 4  
 5 int n;
 6  
 7 int bSearch(int p,int low, int high) {
 8     if(low < high) {
 9         int mid = (low + high)/2;
10         if(minNum[mid] == p) {
11           return mid;
12         }
13         else if(minNum[mid] < p) {
14           return bSearch(p, mid+1, high);
15         }
16         else {
17           return bSearch(p, low, mid-1);
18         }
19     }
20     return low;
21 }
22  
23 int main(int argc, char const *argv[])
24 {
25     while(scanf("%d",&n) != EOF) {
26         for(int i = 0; i < n ; i++) {
27           scanf("%d",&toDo[i]);
28         }
29  
30         minNum[1] = toDo[0];
31         int k = 1;
32         for(int i = 1; i < n; i++) {
33             if(toDo[i] > minNum[k]) {
34                 minNum[++k] = toDo[i];
35             }
36             else {
37                 int p = bSearch(toDo[i], 1, k);
38                 minNum[p] = toDo[i];
39             }
40         }
41         printf("%d
",k);
42     }   
43     return 0;
44 }

经过实验,发现测试数据为 4 5 6 1 2 3 5就会出错,

检查之后发现是二分搜索的错误,当minNum为 1 5 6时, 2 的到来使它变为2 5 6, 而不是1 2 6

修改在17行

代码如下

 1 #include <cstdio>
 2 int toDo[100002];
 3 int minNum[100002];
 4 
 5 int n;
 6 
 7 int bSearch(int p,int low, int high) {
 8     if(low < high) {
 9         int mid = (low + high)/2;
10         if(minNum[mid] == p) {
11           return mid;
12         }
13         else if(minNum[mid] < p) {
14           return bSearch(p, mid+1, high);
15         }
16         else {
17           return bSearch(p, low, mid);
18         }
19     }
20     return low;
21 }
22 
23 int main(int argc, char const *argv[])
24 {
25     while(scanf("%d",&n) != EOF) {
26         for(int i = 0; i < n ; i++) {
27           scanf("%d",&toDo[i]);
28         }
29 
30         minNum[1] = toDo[0];
31         int k = 1;
32         for(int i = 1; i < n; i++) {
33             if(toDo[i] > minNum[k]) {
34                 minNum[++k] = toDo[i];
35             }
36             else {
37                 int p = bSearch(toDo[i], 1, k);
38                 minNum[p] = toDo[i];
39             }
40         }
41         printf("%d
",k);
42     }   
43     return 0;
44 }

事实上,不需要toDo这个数组,代码如下

 1 #include <cstdio>
 2 int toDo;
 3 int minNum[100002];
 4 int n;
 5 
 6 int bSearch(int p,int low, int high) {
 7     if(low < high) {
 8         int mid = (low + high)/2;
 9         if(minNum[mid] == p) {
10           return mid;
11         }
12         else if(minNum[mid] < p) {
13           return bSearch(p, mid+1, high);
14         }
15         else {
16           return bSearch(p, low, mid);
17         }
18     }
19     return low;
20 }
21 
22 int main(int argc, char const *argv[])
23 {
24     while(scanf("%d",&n) != EOF) {
25         int k = 0;
26         for(int i = 0; i < n ; i++) {
27           scanf("%d",&toDo);
28           if(k == 0) {
29              minNum[++k] = toDo;
30              continue;
31           }
32           if(toDo > minNum[k]) {
33                 minNum[++k] = toDo;
34             }
35             else {
36                 int p = bSearch(toDo, 1, k);
37                 minNum[p] = toDo;
38             }
39         }
40         printf("%d
",k);
41     }   
42     return 0;
43 }

 今天看书,知道有个函数是lower_band(), upper_band(),fill();

需要#include <algorithm>

using namespace std

lower_band()从已排好序的序列a中利用二分搜索(区间左开右闭)找出指向满足ai>=k的ai的最小指针

lower_band(a, a+n, k)

可以写出如下代码

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 int toDo;
 5 int dp[100002];
 6 int n;
 7 
 8 int main(int argc, char const *argv[])
 9 {
10     while(scanf("%d",&n) != EOF) {
11         int k = 0;
12         for(int i = 0; i < n ; i++) {
13           scanf("%d",&toDo);
14           if(k == 0) {
15             dp[k++] = toDo;
16           }
17           else {
18             int *p = lower_bound(dp,dp+k,toDo);
19             if(p - dp == k) {
20               dp[k++] = toDo;
21             }
22             else {
23               *p = toDo;
24             }
25           }
26           
27         }
28         
29         printf("%d
",k);
30     }   
31     return 0;
32 }

甚至是

 1 #include <cstdio>
 2 #include <algorithm>
 3 #define inf 2147483647
 4 using namespace std;
 5 int toDo;
 6 int dp[100002];
 7 int n;
 8 
 9 int main(int argc, char const *argv[])
10 {
11     while(scanf("%d",&n) != EOF) {
12         int k = 0;
13         fill(dp, dp+n,inf);
14         for(int i = 0; i < n ; i++) {
15           scanf("%d",&toDo);
16           *lower_bound(dp,dp+n,toDo) = toDo;
17         }
18         
19         printf("%d
",lower_bound(dp, dp+n, inf) - dp);
20     }   
21     return 0;
22 }
原文地址:https://www.cnblogs.com/jasonJie/p/5783373.html