标签:
最长递增子序列问题 在一列数中寻找一些数满足 任意两个数a[i]和a[j] 若i<j 必有a[i]<a[j] 这样最长的子序列称为最长递增子序列LIS
LIS问题有两种常见的解法 一种时间复杂度n^n 一种时间复杂度nlogn
下面我们先来说一下n^n的算法
设dp[i]表示以i结尾的最长上升子序列的长度 把问题分解 分解成序列中每一项最为终点的最大上升子序列 从第二项开始依次判断 最后找出最大的一项就是答案 则状态转移方程为
dp[i] = max{dp[j]+1}, 1<=j<i,a[j]<a[i].
代码时间参考POJ2533
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn = 1011; int an[maxn]; int dp[maxn]; int res; int main(){ int n; scanf("%d", &n); for(int i = 1; i <= n; ++i){ scanf("%d", &an[i]); } for(int i = 1; i <= n; ++i){ dp[i] = 1; for(int j = 1; j < i; ++j){ if(an[j] < an[i]){ dp[i] = max(dp[i], dp[j] + 1); } } res = max(dp[i], res); } printf("%d\n", res); return 0; }
下面我们来说一下另外一种效率更高的算法 时间复杂度nlogn
假设存在一个序列a[1..9] = 2 1 5 3 6 4 8 9 7 可以看出来它的LIS长度为5
我们定义一个序列dp然后令 i = 1 to 9 逐个考察这个序列
我们用一个变量len来记录现在最长算到多少了
首先 把a[1]有序地放到dp里 令dp[1] = 2 就是说当只有1一个数字2的时候 长度为1的LIS的最小末尾是2 这时len=1
然后 把a[2]有序地放到dp里 令dp[1] = 1 就是说长度为1的LIS的最小末尾是1 a[1]=2已经没用了 很容易理解吧 这时len=1
接着 a[3] = 5 a[3]>dp[1] 所以令dp[1+1]=dp[2]=a[3]=5 就是说长度为2的LIS的最小末尾是5 这时候dp[1..2] = 1 5 len=2
再来 a[4] = 3 它正好加在1,5之间 放在1的位置显然不合适 因为1小于3 长度为1的LIS最小末尾应该是1 这样很容易推知 长度为2的LIS最小末尾是3 于是可以把5淘汰掉 这时候dp[1..2] = 1, 3 len = 2
继续 a[5] = 6 它在3后面 因为dp[2] = 3 而6在3后面 于是很容易可以推知dp[3] = 6 这时dp[1..3] = 1, 3, 6,还是很容易理解吧 len = 3
第6个 a[6] = 4 你看它在3和6之间 于是我们就可以把6替换掉 得到dp[3] = 4 dp[1..3] = 1, 3, 4 len继续等于3
第7个 a[7] = 8 它很大 比4大 嗯 于是dp[4] = 8 len变成4了
第8个 a[8] = 9 得到dp[5] = 9 len继续增大到5了
最后一个 a[9] = 7 它在dp[3] = 4和dp[4] = 8之间 所以我们知道 最新的dp[4] =7 dp[1..5] = 1, 3, 4, 7, 9 len = 5
于是我们知道了LIS的长度为5
注意 这个1,3,4,7,9不是LIS 它只是存储的对应长度LIS的最小末尾 有了这个末尾 我们就可以一个一个地插入数据 虽然最后一个a[9] = 7更新进去对于这组数据没有什么意义 但是如果后面再出现两个数字 8 和 9 那么就可以把8更新到a[5] 9更新到a[6] 得出LIS的长度为6
然后应该发现一件事情了 在dp中插入数据是有序的 而且是进行替换而不需要挪动——也就是说 我们可以使用二分查找, 将每一个数字的插入时间优化到O(logN)~~~~~于是算法的时间复杂度就降低到了O(NlogN)~!
附上代码实现 先是HDU1950 比较常规的写法
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn = 40010; int an[maxn], dp[maxn], ans; int main(){ int t; scanf("%d", &t); while(t--){ int n; scanf("%d", &n); for(int i = 1; i <= n; ++i){ scanf("%d", &an[i]); } dp[1] = an[1]; ans = 1; for(int i = 2; i <= n; ++i){ int st = 1; int en = ans + 1; while(st < en){ int mi = (st+en) / 2; if(an[i] <= dp[mi]){ en = mi; } else{ st = mi + 1; } } dp[en] = an[i]; if(en == ans + 1){ ++ans; } } printf("%d\n", ans); } return 0; }
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 1011; const int inf = 0xffffff; int an[maxn]; int dp[maxn]; int main(){ int n; scanf("%d", &n); for(int i = 0; i < n; ++i){ scanf("%d", &an[i]); } fill(dp, dp+n, inf); for(int i = 0; i < n; ++i){ *lower_bound(dp, dp+n, an[i]) = an[i]; } printf("%d\n", lower_bound(dp, dp+n, inf) - dp); return 0; }
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:
原文地址:http://blog.csdn.net/u012431590/article/details/46997161