最近一直在做《挑战程序设计竞赛》的练习题,感觉好多经典的题,都值得记录。
题意:给你t组数据,每组数组有n个数字,求每组的最长上升子序列的长度。
思路:由于n最大为40000,所以n*n的复杂度不够了,会超时。
书上状态方程换成了d[i]——以长度为i+1的上升子序列中末尾元素的最小值。
那么我们在遍历第i个元素时候,以这个元素为末尾元素的最长子序列也就是在d[i]中找到一个小于num[i]的最大值,然后在这个序列末尾加上num[i]
显然,我们在查找时便可以利用二分搜索,从而把复杂度从原来的n变为了logn,总复杂度从n*n变成了nlogn
d[i]已经保证了长度为i+1的上升子序列末尾元素的最小值,那么对于d[i+1]长度为i+2的子序列里面,要获得最长,自然就要从长度为i+1的子序列中,挑选末尾元素为最小的子序列后面添加元素。所以d[i+1] > d[i],d数组是一个递增的数组,所以就能用二分搜索了。
lower_bound(d,d+n,num[i]); //默认数组d为上升数组,返回第一个大于等于num[i]的指针。
lower_bound(d,d+n,num[i],greater<int>()); //表达数组d为下降数组,返回第一个小于等于num[i]的指针。
AC代码:
#include <cstdio> #include <algorithm> #include <iostream> using namespace std; const int N = 40005; const int INF = 0X3F3F3F3F; int n,t,num[N],d[N]; //d[i] = 长度为i+1的上升子序列中末尾元素的最小值,不存在则INF void solve() { for(int i = 0; i < n; i++) d[i] = INF; for(int i = 0; i < n; i++) { scanf("%d", num+i); *lower_bound(d,d+n,num[i]) = num[i]; } printf("%d ", lower_bound(d,d+n,INF) - d); } int main() { scanf("%d", &t); while(t--) { scanf("%d", &n); solve(); } return 0; }