nyoj 单调递增子序列(二) 214 (LIS 优化)
来源:互联网 发布:程序员是一个年轻人的 编辑:程序博客网 时间:2024/05/29 04:47
单调递增子序列(二)
- 描述
给定一整型数列{a1,a2...,an}(0<n<=100000),找出单调递增最长子序列,并求出其长度。
如:1 9 10 5 11 2 13的最长单调递增子序列是1 9 10 11 13,长度为5。
- 输入
- 有多组测试数据(<=7)
每组测试数据的第一行是一个整数n表示序列中共有n个整数,随后的下一行里有n个整数,表示数列中的所有元素.每个整形数中间用空格间隔开(0<n<=100000)。
数据以EOF结束 。
输入数据保证合法(全为int型整数)! - 输出
- 对于每组测试数据输出整形数列的最长递增子序列的长度,每个输出占一行。
- 样例输入
71 9 10 5 11 2 1322 -1
- 样例输出
51
//还是那位学长的讲解,,,真心很好。。。
在第一种算法中,在计算每一个f(i)时,都要找出最大的f(j)(j<i)来,由于f(j)没有顺序,只能顺序查找满足aj<ai最大的f(j),如果能将让f(j)有序,就可以使用二分查找,这样算法的时间复杂度就可能降到O(nlogn)。于是想到用一个数组B来存储“子序列的”最大递增子序列的最末元素,即有B[f(j)] = aj;
在计算f(i)时,在数组B中用二分查找法找到满足j<i且B[f(j)]=aj<ai的最大的j,并将B[f[j]+1]置为ai。下面先写出代码,再证明算法的证明性。
#include<stdio.h>#include<string.h>#define MIN -32769int a[100010];int main(){int n,m;int i,j;int top;int l,r,mid;memset(a,0,sizeof(a));while(scanf("%d",&n)!=EOF){top=0;a[0]=MIN;for(i=0;i<n;i++){scanf("%d",&m);if(m>a[top])//如果输入进来的数比栈顶的数大,直接插入到栈的a[++top]=m;else{l=1;r=top;while(l<=r)//二分查找,寻找插入位置{mid=(l+r)/2;if(m>a[mid])l=mid+1;elser=mid-1;}a[l]=m;//找到插入位置,并替换点原值}}printf("%d\n",top);}return 0;}
现在来证明这个算法为什么是正确的。要使算法正确只须证如下命题:
命题1:每一次循环结束数组B中元素总是按递增顺序排列的。
证明:用数学归纳法,对循环次数i进行归纳。
当i=0时,即程序还没进入循环时,命题显然成立。
设i<k时命题成立,当i=k时,假设存在j1<j2,B[j1]>B[j2],因为第i次循环之前数组B是递增的,因此第i次循环时B[j1]或B[j2]必有一个更新,假设B[j1]被更新为元素ai+1,由于ai+1=B[j1]> B[j2],按算法ai+1应更新B[j2]才对,因此产生矛盾;假设B[j2]被更新,设更新前的元素为s,更新后的元素为ai+1,则由算法可知第i次循环前有B[j2]=s< ai+1< B[j1],这与归纳假设矛盾。命题得证。
命题2:B[c]中存储的元素是当前所有最长递增子序列长度为c的序列中,最小的最末元素,即设当前循环次数为i,有B[c]={aj| f(k)=f(j)=c∧k,j≤i+1→aj≤ak}(f(i)为与第二种算法中的f(i)含义相同)。
证明:程序中每次用元素ai更新B[c]时(c=f(i)),设B[c]原来的值为s,则必有ai<s,不然ai就能接在s的后面形成长度为c+1的最长递增子序列,而更新B[c+1]而不是B[c]了。所有B[c]中存放的总是当前长度为c的最长递增子序列中,最小的最末元素。
命题3:设第i次循环后得到的p为p(i+1),那么p(i)为以元素ai为最末元素的最长递增子序列的长度。
证明:只须证p(i)等于第二种算法中的f(i)。显然一定有p(i)<=f(i)。假设p(i)<f(i),那么有两种情况,第一种情况是由二分查找法找到的p(i)不是数组B中能让ai接在后面成为新的最长递增子序列的最大的元素,由命题1和二分查找的方法可知,这是不可能的;第二种情况是能让ai接在后面形成长于p(i)的最长递增子序列的元素不在数组B中,由命题2可知,这是不可能的,因为B[c]中存放的是最末元素最小的长度为c的最长递增子序列的最末元素,若ai能接在长度为L(L> p(i))的最长递增子序列后面,就应该能接在B[L]后面,那么就应该有p(i)=L,与L> p(i)矛盾。因此一定有p(i)=f(i),命题得证。
算法的循环次数为n,每次循环二分查找用时logn,所以算法的时间复杂度为O(nlogn)。这个算法在第二种算法的基础上得到了较好的改进。
如果证明看晕了,暂时先用一组数据进行形象比较,就可以理解代码含义了。。。。(其实我也看晕了,嘿嘿)
假设存在一个序列d[1...9]=2 1 5 3 6 4 8 9 7,可以看出它的LIS长度为5.
下面一步一步试着找出它。
我们定义一个序列B,然后令 i = 1 to 9 逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了
首先,把d[1]有序地放到B里,令B[1] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1;
然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1;
接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1..2] = 1, 5,Len=2
再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1..2] = 1, 3,Len = 2
继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1..3] = 1, 3, 6,还是很容易理解吧? Len = 3 了噢。
第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1..3] = 1, 3, 4, Len继续等于3
第7个, d[7] = 8,它很大,比4大,嗯。于是B[4] = 8。Len变成4了
第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。
最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。
于是我们知道了LIS的长度为5。
!!!!!注意。这个1,3,4,7,9不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个d[9] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到d[5], 9更新到d[6],得出LIS的长度为6。
然后应该发现一件事情了:在B中插入数据是有序的,而且是进行替换而不需要挪动——也就是说,我们可以使用二分查找,将每一个数字的插入时间优化到O(logN)~~~~~于是算法的时间复杂度就降低到了O(NlogN)~!
- nyoj 单调递增子序列(二) 214 (LIS 优化)
- NYOJ 214 单调递增子序列(二) (LIS模板)
- nyoj 214 单调递增子序列(二) 【LIS】
- nyoj--214--单调递增子序列(二)(二分查找+LIS)
- nyoj 214 单调递增子序列(二)《LIS》
- 214 单调递增子序列(二)【lis】
- nyoj 214 单调递增子序列(二)(LIS ---nlogn算法)
- NYOJ 214 单调递增子序列(二)
- NYOJ 214 单调递增子序列(二)
- NYOJ 214 单调递增子序列二
- Nyoj 214 单调递增子序列(二)
- NYOJ 214 单调递增子序列(二)
- NYOJ 214 单调递增子序列 二
- 单调递增子序列(二)(nyoj 214)
- NYOJ 214 单调递增子序列(二)
- nyoj-214 单调递增子序列(二)
- NYOJ 214 单调递增子序列(二)
- nyoj 214单调递增子序列(二)
- iOS view 之 frame 和 bounds
- CentOS 6.6下的J2EE环境搭建(五)之MySQL安装
- SQLite数据库
- 页面设置与LRU
- 试着学习写博客(test)
- nyoj 单调递增子序列(二) 214 (LIS 优化)
- [Unity3D学习]3D数学基础之坐标系
- C# webbrowser中json后缀的文件不能显示的问题
- Ubuntu搭建C/C++编程环境
- 时尚好用的---窗帘效果 和 点击弹出登录界面的效果 (Scroller类的应用) 附 2个DEMO及源码
- 如何捕捉热点及个股(转载)-1
- OpenStack Nova启动实例流程
- Ubuntu 目录
- Spring整合Quartz,并持久化Job到MySQL