最长上升子序列LIS(51nod1134)

基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题
收藏
关注
给出长度为N的数组,找出这个数组的最长递增子序列。(递增子序列是指,子序列的元素是递增的)
例如:5 1 6 8 2 4 5 10,最长递增子序列是1 2 4 5 10。
 
Input
第1行:1个数N,N为序列的长度(2 <= N <= 50000)
第2 - N + 1行:每行1个数,对应序列的元素(-10^9 <= S[i] <= 10^9)
Output
输出最长递增子序列的长度。
Input示例
8
5
1
6
8
2
4
5
10
Output示例
5
这个问题是被称作最长上升子序列的著名问题。解决方法有多种,这里介绍两种,
第一种是时间复杂度为O(n^2)的,不过这里我提交了,超时了,但是也简单介绍下,这种方法比较简单,只有我们知道DP,一般都可以想到,将dp[i]定义为以a[i]结尾的最长上升子序列的长度,而已a[i]结尾的上升子序列可以分为两种,一种是只包含a[i]的子序列,另一种是满足j<i并且a[j]<a[i]的以a[j]为结尾的上升子列末尾,尾加上a[i]后的得到的子序列,然后我们就可以得到递推关系dp[i]=max{1,dp[j]+1|j<i且a[j]<a[i]},接下来写代码就很简单了。
 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 using namespace std;
 5 const int maxn=1000005;
 6 int a[maxn]; 
 7 int dp[maxn];  //dp[i]表示以a[i]为末尾的最长上升子序列长度 
 8 int n;
 9 
10 int main()
11 {
12     cin>>n;
13     for(int i=1;i<=n;i++) cin>>a[i];
14     int ans=0;
15     for(int i=1;i<=n;i++)
16     {
17         dp[i]=1;
18         for(int j=1;j<i;j++)
19         {
20             if(a[j]<a[i])
21                 dp[i]=max(dp[i],dp[j]+1);
22         }
23         ans=max(ans,dp[i]);
24     }
25     cout<<ans<<endl;
26     return 0;
27 } 

而这题需要用的是第二种方法,就是看这篇博客里的:http://www.cnblogs.com/mengxm-lincf/archive/2011/07/12/2104745.html

还有这个ty大佬同学博客里的:https://www.cnblogs.com/tuyang1129/p/9282657.html

最长递增子序列,Longest Increasing Subsequence 下面我们简记为 LIS。
排序+LCS算法 以及 DP算法就忽略了,这两个太容易理解了。

假设存在一个序列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 //最长上升子序列LIS 
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<algorithm>
 5 #include<cstring>
 6 using namespace std;
 7 const int maxn=1000005;
 8 int n;
 9 int dp[maxn];  //dp[i]表示整个序列中长度为i的最长子序列的末尾元素 最小值 
10 
11 int main()
12 {
13     cin>>n;
14     int num; 
15     cin>>num;
16     dp[0]=num;
17     int cnt=1;
18     for(int i=1;i<n;i++)
19     {
20         cin>>num;
21         if(num>dp[cnt-1]) dp[cnt++]=num;
22         else
23         {
24             int p=lower_bound(dp,dp+cnt-1,num)-dp;
25             dp[p]=num;
26         }
27         /*二分法找第一个大于等于num的位置 
28         else
29         {
30             int low=0,high=cnt-1;
31             int pos;  //记录位置 
32             while(low<high)
33             {
34                 int mid=(low+high)/2;
35                 if(dp[mid]>=num)  //因为要求第一个大于等于num的位置,所以当dp[mid]==num时,我们要向左找第一个 
36                 {
37                     high=mid;//不能是mid-1,因为可能会比到小于num的地方去 
38                     pos=high;
39                  } 
40                 else //往右边找第一个等于k的值  
41                 {
42                     low=mid+1;
43                     pos=low;
44                 }
45             }
46             dp[pos]=num;
47         }*/
48     }
49     cout<<cnt<<endl;
50     return 0;
51 } 

 C++STL中的upper_bound()函数和lower_bound()的使用与理解

如果要自己写这连个函数,可以参考以下的代码:

upper_bound()函数返回第一个大于x的位置

int upper_bound(int l,int r,int x)
{  while(l<r){
    int mid=(l+r)/2;
    if(a[mid]>x)r=mid;
    else l=mid+1;
}
return l;
}

lower_bound()函数返回第一个大于等于x的位置

int lower_bound(int l,int r,int x)
{  while(l<r){
    int mid=(l+r)/2;
    if(a[mid]>=x)r=mid;
    else l=mid+1;
}
return l;
}

lower_bound()函数使用:

它的参数就是:

1.一个数组元素的地址(或者数组名来表示这个数组的首地址,用来表示这个数组的开头比较的元素的地址,不一定要是首地址,只是用于比较的“首”地址),

2.一个数组元素的地址(对应的这个数组里边任意一个元素的地址,表示这个二分里边的比较的"结尾'地址),

3.就是一个你要二分查找的那个数。

返回值:

返回值就是返回第一次出现大于等于那个要查找的数的地址,

注意两点,

第一,是地址,不是指那个要查找的数的下标,所以就注定了在这个函数的后边就要减去一个尾巴,那就是这个数组的数组名,即这个数组的首地址,只有这样才代表那个要查找的数字的下标,当然如果没有找到那个数,也是会返回的,那么返回的又会是什么呢?下面第二点。

第二点,那就是要大于等于那个数,等于好理解,大于怎么理解呢,比如说我并没有找到那个数,加入一个的数组里边就有5个数,分别是1,1,1,3,5,而我需要找的那个数就是2,怎么返回呢?小编告诉你哦,就是返回那个第一个大于2的数的地址,就是返回3的地址,那么再有一组数据就是5个数1,1,1,3,3,还是需要找寻2,那么该返回什么呢?那就是第一个3的地址。下边来段代码你理解下吧

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int k,n=10;
int a[10]={1,1,1,3,3,5,5,5,5,6};
int main()
{
    for(int i=0;i<n;i++)cout<<a[i]<<" ";
    cout<<endl;
   while(scanf("%d",&k))
   {
       cout<<k<<"的第一个大于等于它的位置在"<<((lower_bound(a,a+n,k))-a)+1<<endl;
   }
}

upper_bound函数的用法lower_bound函数的用法相似,不过这个唯一的不同就是返回的是第一个比我要找的那个数大的数的地址,注意,这里并没有等于,也就是说如果在5个数1,1,2,2,4,里边寻找3,那么就会返回4的地址,下边代码实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int k,n=10;
int a[10]={1,1,1,3,3,5,5,5,5,6};
int main()
{
    for(int i=0;i<n;i++)cout<<a[i]<<" ";
    cout<<endl;
   while(scanf("%d",&k))
   {
       cout<<k<<"的第一个大于它的位置在"<<((upper_bound(a,a+n,k))-a)+1<<endl;
   }
}

参考自博客:https://blog.csdn.net/sdz20172133/article/details/80101838

原文地址:https://www.cnblogs.com/zjl192628928/p/9345677.html