最长上升子序列(LIS)动态规划
Posted Dh_q
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最长上升子序列(LIS)动态规划相关的知识,希望对你有一定的参考价值。
最长上升子序列
给你n个整数 A1 A2 ········· An 找出在这个数组里面的最长上升的子序列。例如给你(1,7,3,5,9,4,8),他的上升子序列有(1,7) (3,4,8)等等之类的,但是最长的上升子序列是(1,3,5,8)。
1)n^2算法
dp[i] 为当前数组里第i个元素时,以a[i]元素为结尾的最长子序列长度。
动态转移方程是:dp[i] = max ( dp[i] , dp[j] + 1 ) 其中 j<i 且 a[j] < a[i] 。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cstdlib> 5 #include <string> 6 #include <vector> 7 #include <map> 8 #include <set> 9 #include <queue> 10 #include <sstream> 11 #include <algorithm> 12 using namespace std; 13 #define pb push_back 14 #define mp make_pair 15 #define ms(a, b) memset((a), (b), sizeof(a)) 16 //#define LOCAL 17 typedef long long LL; 18 const int inf = 0x3f3f3f3f; 19 const int maxn = 1000+10; 20 const LL mod = 1000000000+7; 21 int a[maxn]; 22 int dp[maxn]; 23 int main() 24 { 25 #ifdef LOCAL 26 freopen("input.txt" , "r", stdin); 27 #endif // LOCAL 28 int n; 29 scanf("%d", &n); 30 for(int i=0;i<n;i++) scanf("%d", &a[i]); 31 dp[0]=1; 32 for(int i=1;i<n;i++) 33 { 34 dp[i] = 1; 35 for(int j=0;j<i;j++) 36 { 37 if(a[j]<a[i] && dp[j]+1 > dp[i]) 38 dp[i] = dp[j] + 1; 39 } 40 } 41 int ans = 0; 42 for(int i=0;i<n;i++) ans = max(ans, dp[i]); 43 printf("%d\n", ans); 44 return 0; 45 }
2)nlogn算法
假设存在一个序列d[1..9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。n
下面一步一步试着找出它。
我们定义一个序列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)~!
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cstdlib> 5 #include <string> 6 #include <vector> 7 #include <map> 8 #include <set> 9 #include <queue> 10 #include <sstream> 11 #include <algorithm> 12 using namespace std; 13 #define pb push_back 14 #define mp make_pair 15 #define ms(a, b) memset((a), (b), sizeof(a)) 16 //#define LOCAL 17 typedef long long LL; 18 const int inf = 0x3f3f3f3f; 19 const int maxn = 50000+10; 20 const LL mod = 1000000000+7; 21 int a[maxn]; 22 int dp[maxn]; 23 int Binary_search(int key, int len) 24 { 25 int l=1, r=len+1; 26 while(l<r) 27 { 28 int middle = (l+r) >> 1; 29 if(key>=dp[middle]) 30 l = middle +1; 31 else 32 r = middle; 33 } 34 return l; 35 } 36 int main() 37 { 38 #ifdef LOCAL 39 freopen("input.txt" , "r", stdin); 40 #endif // LOCAL 41 int n, len; 42 scanf("%d", &n); 43 for(int i=0;i<n;i++) scanf("%d", &a[i]); 44 dp[1] = a[0]; 45 len = 1; 46 for(int i=1;i<n;i++) 47 { 48 if(a[i] > dp[len]) 49 dp[++len] = a[i]; 50 else{ 51 int j = Binary_search(a[i], len); 52 dp[j] = a[i]; 53 } 54 } 55 printf("%d\n", len); 56 return 0; 57 }
附上一些练习题:
1)https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1134
2)http://poj.org/problem?id=2533
3)http://poj.org/problem?id=1631
4)http://poj.org/problem?id=1887(最长不上升子序列)
5)http://poj.org/problem?id=1609(最长不上升子序列)
这些题的题解稍后放出。
以上是关于最长上升子序列(LIS)动态规划的主要内容,如果未能解决你的问题,请参考以下文章