Longest Ordered Subsequence POJ

来源:互联网 发布:2017网络机顶盒哪个好 编辑:程序博客网 时间:2024/06/05 17:01

Longest Ordered Subsequence

POJ - 2533

A numeric sequenceof ai is ordered if a1 <a2< ... < aN. Let the subsequence of the given numericsequence (a1, a2, ..., aN)be any sequence (ai1, ai2, ..., aiK),where 1 <=i1 < i2 < ... < iK<= N. For example, sequence (1, 7, 3, 5, 9, 4, 8) has orderedsubsequences, e. g., (1, 7), (3, 4, 8) and many others. All longest orderedsubsequences are of length 4, e. g., (1, 3, 5, 8).

Your program, when given the numeric sequence, must find the length of itslongest ordered subsequence.

Input

The first line ofinput file contains the length of sequence N. The second line contains theelements of sequence - N integers in the range from 0 to 10000 each, separatedby spaces. 1 <= N <= 1000

Output

Output file mustcontain a single integer - the length of the longest ordered subsequence of thegiven sequence.

Sample Input

7

1 7 3 5 9 4 8

Sample Output

4

题意:这个题就是经典的求最长上升子序列的问题,这个子序列可以是不连续的只需要满足i < j && a[i] < a[j]

下面介绍两种方法求解并加上我的理解

方法1:

这种方法是最普遍的想法,也很容易理解

定义dp[i] = 以ai为末尾的最长上升子序列,那么在以ai结尾的这个序列中,如果存在这么一个aj,使得j < i(这个条件可以通过循环条件硬性规定),并且aj < ai(这个就需要用if语句判断),那么此时以ai为结尾的最长上升子序列长度就是原本以aj为末尾的长度加一即dp[j]+1,否则等于它本身dp[i],每个dp[i]都初始化为1,代表只有他自身一个长度为一

那么这个问题就很清晰了下面是代码

code1:

#include <iostream>#include <cstdio>#include <cstring>using namespace std;#define MAXN 1009int dp[MAXN];//dp[i]代表了一ai为结尾的递增序列的长度int a[MAXN];int n;int main(){    int i,j;    cin >> n;    for(i = 0; i< n; i++)        cin>> a[i];    int res = 0;    for(i = 0; i< n; i++){        dp[i] = 1;        for(j = 0;j < i; j++){            if(a[j]< a[i]){//满足 j<i && a[j] < a[i],那么dp[i] = 以aj为结尾的最长长度加1               dp[i] = max(dp[i],dp[j]+1);            }        }        res =max(res,dp[i]);//得到以当前ai为结尾的最长长度后选出最大的    }    cout <<res << endl;    return 0;}



方法2:

这个方法很巧妙,代码也很简洁,但是理解起来可能需要一定时间

我们重新定义dp[i] = 长度为i+1的上升子序列中末尾元素的最小值(不存在的话就是INF),单单是看这句话你可能就会有很多疑问

第一为什么是长度为i+1而不是i,其实这也是这个写法的巧妙之处,一会看代码的时候你会发现dp[i]的i是从0开始的,所以当然是代表长度i+1了。

第二为什么是最小值呢,因为如果子序列长度相同,那么最末尾元素较小的在之后求更长的会更有优势,因为我们就可以在这个的基础上求更长的了,如果你找了最大的,那长度就很难再变了

第三不存在怎么是INF,因为我们要dp定义的是末尾元素的最小值嘛,所以如果不存在我们就把他弄的大一些就可以了啊,所以就定义INF

那么接下来如果更新这个dp数组呢

首先初始化都为INF,然后我们是从前往后这个考虑元素的,所以对于每一个aj,如果i=0或者dp[i-1]<aj的话,说明什么呢?说明我们是不是可以把aj这个数放在dp[i]上了,也就是说成功的是长度增加了一个,这也又一次说明了我们为什么要用末尾元素的最小值。再说一遍aj在当前的dp数组找了一圈发现aj是最大的,所以aj放在了dp[i]上,那么反之,如果aj在dp数组里找了一圈发现自己不是最大的,大家想一想会怎么样呢,首先长度肯定不会增加了对吧,dp[i]还是INF,那么也一定说明aj肯定比dp中的某个数小对吧,再返回定义dp定义的是末尾元素最小对吧,那我们是不是把当前dp中第一个比aj大的数改成aj是不是就可以了,是的就是这样!记住这个分析

仔细想一想上面红色字标注的分析,你发现了什么,我从前往后一个一个的看元素aj,是不是就是在dp数组中找第一个大于或者等于aj的数!!!,你又想到了什么,没错,这不就是c++提供的stl中lower_bound函数的用法吗,所以这个代码用lower_bound函数就可以实现啦,就是从前往后一个一个看,每个元素用lower_bound在dp里找,然后把当前aj

赋值给他即更新,那你可能又有疑问,lower_bound就是二分查找,必须是有序数列,那么这个dp是有序的吗,答案是肯定的,我问你,一开始都是INF,什么情况下才会把一个INF更改成原来数列中的一个数,当全是INF的时候对吧,其次呢,是不是如果这个数比原来已有的数都大,那么我用lower_bound找大于等于它的数是不是只有INF了,因此从一开始一直往后地推下去,dp数组一定是一个递增的序列(不断和lower_bound的查找规则联系起来想),那么对于dp的更新操作就完了,怎么输出答案

答案是不是就是最后一个的非INF的dp[i]的下标加一即一开始定义的i+1,但是我们并不知道最后一个是多少怎么找,一个方法遍历一边,下一个是INF就停止,然后输出i+1

其实还有更快的,直接用lower_bound找INF,那么它找到的肯定是第一个INF对吧,那么这个下标是不是恰好就是长度了(后移了一个嘛),但是lower_bound返回的是迭代器啊,直接-dp不就行了嘛,当指针用嘛

说了上面一堆,也不知道你们能不能看得下去,看没看得懂,反正看代码吧

code2

 

#include <iostream>#include <cstring>#include <algorithm>using namespace std;#define MAXN 1009#define INF 0x3f3f3f3fint dp[MAXN];int a[MAXN];int main(){    inti;    intn;    cin>> n;   for(i = 0; i < n; i++){       cin >> a[i];    }   memset(dp,INF,sizeof(dp));   for(i = 0; i < n; i++){       *lower_bound(dp,dp+n,a[i]) = a[i];//二分查找需要修改的点,并修改为当前值    }   cout << lower_bound(dp,dp+n,INF)-dp << endl;//找到第一个INF值,减去首地址就是实际长度,因为是数组是从0开始的,所以恰好是加一个到第一个INF值的下标   return 0;}