- 题目描述:
-
给定一个整型数组, 求这个数组的最长严格递增子序列的长度。 譬如序列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 }