最长上升子序列 LIS
Posted SuperChan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最长上升子序列 LIS相关的知识,希望对你有一定的参考价值。
题目:
http://acm.nyist.net/JudgeOnline/problem.php?pid=17
http://poj.org/problem?id=2533
两道题几乎一样,只不过对于输入输出的要求有所不同罢了。
LIS有两种方法:
一、第一种方法 · 时间复杂度为O(n^2):
状态:dp[i] := 区间为0~i的序列的LIS
转移方程:dp[i] = max(1, dp[k] + 1) (0<=k<i && s[k]<s[i])
#include <iostream> #include <cstdio> #include <cstring> #include <string> using namespace std; char s[10005]; int dp[10005]; int main () { int n; scanf("%d", &n); while(n--) { scanf("%s", &s); int len = strlen(s); for(int i=0; i<len; i++) { dp[i] = 1; for(int j=0; j<i; j++) { if(s[j] < s[i]) { dp[i] = max(dp[i], dp[j]+1); } } } int ans = 0; for(int i=0; i<len; i++) ans = max(ans, dp[i]); printf("%d\n", ans); } return 0; }
二、第二种方法 · 时间复杂度为O(nlgn):
这种方法需要用二分查找查询到当前这一位元素应该是长度为多少的上升子序列的最后一位。
状态定义:dp[i] := 长度为i的上升子序列的当前最小的末尾元素,这里之所以要找最小的末尾元素,是因为越小越容易在后面加上一个稍大的元素使得找到的LIS更长!换句话说,倘若我已经将末尾元素的值设置为最低,尽力减小后来者的“门槛”了,但长度仍然只有这么多,那么上升子序列的最长长度也就只能是这样了。
举个例子:序列S为{1,4,2}
step1:将数组dp初始化为{+∞,+∞,+∞}
step2:开始遍历序列S
① 当前遍历到的元素为1时,我们在dp序列进行二分查找,寻找第一个大于等于1的元素的下标,我们找到dp[1]=+∞是第一个大于1的元素(dp的下标从1开始),于是dp[1]=1,即:目前来看,长度为1的上升子序列的末尾元素为1
② 当前遍历到的元素为4时,在dp序列中找到第一个大于等于4的元素为dp[2]=+∞,所以dp[2]=4。这时,dp={1,4,+∞,+∞},说明长度为1和2的上升子序列的最小末尾元素都已经找到。
③ 当前遍历到的元素为2时,在dp序列中找到第一个大于等于2的元素为dp[2]=4,这说明元素2没有办法让目前的最长上升序列更长(除非>4),但是相对于它后面的元素而言,它更有“潜质”作为长度为2的上升子序列的末尾元素,因为它比当前的4要小,4能做到的它都可以,而且可能得到比4更长的上升序列,即做得还可能比4要好。
#include <iostream> #include <cstdio> #include <cstring> #include <string> #include <algorithm> #define INF 30 using namespace std; char s[10005]; int dp[10005]; int main () { int n; scanf("%d", &n); while(n--) { scanf("%s", &s); int len = strlen(s); for(int i=0; i<len; i++) { dp[i] = INF; } for(int i=0; i<len; i++) { *lower_bound(dp, dp+len, s[i]-‘a‘) = s[i]-‘a‘; } printf("%d\n", lower_bound(dp, dp+len, INF) - dp); } return 0; }
以上是关于最长上升子序列 LIS的主要内容,如果未能解决你的问题,请参考以下文章