初做此题时,的确注意到了数据范围,如果用动态规划做的话,两层搜索,时间复杂度为o(n^2),n达50000,铁定超时。但是我还是用dp交了一次,果然tle了。想在10001的基础上优化一下,但是苦于没想法。
龙酱后来跟我提了一下 优先队列 这种数据结构。什么是优先队列?
优先队列(priority queue)
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高进先出(largest-in,first-out)的行为特征。
在优先队列中插入数值和删除数值两种操作的时间复杂度是o(logn),可以大大提高程序效率。具体的在《挑战程序设计竞赛》这本书里面讲的很清楚,我就是看的这本书理解的。
我做这个题时,参考了这篇文章
最长不下降子序列的O(n^2)算法和O(nlogn)算法 - General的日志 - 网易博客(但是此题是求最长单增子序列)。
做法是用了一个数组来存储子序列信息,但是我不清楚那个数组是不是优先队列,还请各位指教!
具体实现是这样的:
len[i]的下标代表在子序列的长度i,len[i]内储存的值是所有长度为i的单增子序列中结尾元素里值最小的那个。lenth记录len[]的当前长度。则序列读完后,len数组的下标最大值,即为最长单增子序列的长度。
读入序列的第一个元素a1首先放入len[1],lenth=1,然后读入第二个元素a2。如果a2>=a1,则lenth++,len[lenth]=a2;不然,len[lenth]=a2。同理于a3,a4,……,an,最后得到的lenth就是答案。
做这个题时还使用了一个技巧,就是一种高效的查找元素的方法,二分查找。
二分查找的先决条件是,作为搜索范围的序列首先应该是有序的。
AC代码:
#include <iostream> #include <cstdlib> #include <cstdio> using namespace std; int len[50001] = {0}; //len[i]表示长度为i的子序列中最小的结尾数 int b_search(int x, int l, int r) { while (l <= r) { int mid = (l + r) >> 1; if (len[mid] <= x && len[mid+1] >= x) return mid + 1; else if (len[mid] > x) r = mid - 1; else l = mid + 1; } return 1; } int main() { int n; while (~scanf("%d", &n)) { int i = 1; int lenth = 1; while (i <= n) { int a; scanf("%d", &a); if (i == 1) { len[1] = a; } else { if (a >= len[lenth]) { lenth++; len[lenth] = a; } else if (a < len[1]) { len[1] = a; } else { int j = b_search(a, 1, lenth); len[j] = a; } } i++; } printf("%d\n", lenth); } return 0; }
后来我看了一下龙酱的,竟然可以优化到46毫秒。看了他的源码,原来是没有用scanf读入而是自己写了个读整数函数,果然牛逼。。