最长上升子序列定义:
给定一个数列(a1,a2,a3,,,an),当a1<a2<a3<...<an时我们称这个数列时上升数列。
若给定一组数(a1,a2,a3,----an),存在一组数(ai1,ai2,,,aik)是一个上升序列,(其中1<=ai1<ai2<....<aik <=n),那么称(ai1,ai2,,,aik)是这组数的一个上升序列,如(1,7,3,5,9,4,8)的其中一个上升序列是(1,3),(3,4,8),而这些上升序列的个数的最大值就是“ 最长上升子序列 ”,如(1,3,4,8)是一组最长上升子序列,他的长度是4。
OpenJudge2757最长上升子序列:
题目要求先输入一个序列,然后且最长上升子序列的个数最大值。
Input:
7
1 7 3 5 9 4 8
Output:
4
思路介绍:
这是一道动规题。以下讨论中我们用ak代表数组a的第k个元素。(即k是数组a的下标)
1.先找子问题:
解法一:”求序列的前n个元素的最长上升子序列长度“,是一种办法。
(但是这个办法不具有”无后效型“,即第n+1个元素插入后,对前n个元素的最长上升子序列长度有较大影响,可能 是最长的,也可能不是。故排除)。
解法二:”求以ak(k = 1,2,3,,,,N)为终点的最长上升子序列的长度,“,本子问题虽然与原问题不同,但是有“无后效 性”,每个点得出的结果是确定的。然后对N个结果找最大值即是所求。
2.确定状态:
一共有N个点,每个点代表着以该点为终点的最长上升子序列的长度,因此,共有N种状态,即每个ak值就是该状态。(这里假设a数组是存放以每个数为节点的最长上升子序列的长度。k即第k个点)。
3.找出状态转换方程:
以maxLen[ i ] 代表以ak作为终点的最长上升子序列的长度。
①。初始状态maxLen[ k ] = 1;
②。maxLen [ k ] = max{maxLen( i ) : 1<= i <k 且ai < ak 且 k!=1)}+1
(maxLen(n)的值,就是在ak左边,终点数值小于ak,且长度最大的上升子序列的长度加1.(因为在ak左边任何终点小于ak的自I序列,加上ak偶就能形成一个更长的上升子序列))
具体做法:
1.首先给出一个数组a存放这几个数,然后给出一个数组maxLen,代表以ai为终点的最长上升子序列的长度。
const int maxn = 1010;
int a[maxn];
int maxLen[maxn];
2.然后输入所有的数,同时还要对每个maxLen[ i ]赋值为1,代表以ai为“终点”的最长上升子序列的长度。初始值为1,即本身的长度。
for(int i=1;i<=n;i++){
cin >> a[i];
maxLen[i] = 1;
}
3. 第一个数最长上升子序列就是1,不用变。然后从2开始(即第 i 位),找第2位左边的,a [ 2]的值大于它的,且 maxLen[ j ]的值加一大于它的最大值的这个数。 (一共三个条件),(如果不好理解,请看下面的推导过程,建议下载下来看。)
/*用到了max函数,需要#include <algorithm> */
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
maxLen[i] = max(maxLen[i],maxLen[j]+1);
}
}
4.然后找出maxLen当中的最大者,即是最长上升子序列的长度。
cout << *max_element(a+1,a+n+1) << endl;
推导过程:(我给了一个图进行模拟。)(建议下载图片推导一遍)
时间复杂度讨论:
dp的时间复杂度 = 状态数 × 求状态所需时间。
显然N个状态,求每个是N次比较,故时间复杂度为O(N²)
代码:
#include <iostream>
#include <algorithm>
using namespace std;
/*OpenJudge 2527*/
const int MAXN = 1010;
int a[MAXN];
int maxLen[MAXN];
int main()
{
int N;
cin >> N;
for(int i=1;i<=N;i++)
{
cin >> a[i];
maxLen[i] = 1; //注1:记住要初始化。
}
for(int i=2;i<=N;i++)
//每次求以第i个数为终点的最长上升子序列的长度
{
for(int j=1;j<i;j++)
//查看以第j个数为终点的最长上升子序列
if( a[i] > a[j])
maxLen[i] = max(maxLen[i],maxLen[j]+1);
}
cout << *max_element(maxLen+1,maxLen+N+1);
return 0;
}