题目链接
https://www.acwing.com/problem/content/897/
思路
闫氏dp分析法,先上图
状态表示图中已经说明,关于状态划分,f [ i ]表示的是所有以第i个数结尾的上升子序列的集合,那么这个集合的所有元素一定都是以第 i 个数结尾的,我们的划分与背包有点相似,以第(i-1)个数是哪个数来分类,那么可以分成:
没有第(i-1)个数,序列长度为1,用0表示。
第(i-1)个数是第1个数,用1表示。
…
第(i-1)个数是第 j 个数,用 j 表示。
…
第(i-1)个数是第(i-1)个数,用(i-1)表示。
注意,上面说的第(i-1)个数,不是说原序列里面第 i 个数的前一个数,是f [ i ]所表示的每一个第 i 个数所表示的最长上升子序列的前一个数,然后这个数对应到原序列是哪一个数。
当然,这里面每一类都不一定存在,如果a [ j ] ≥ a [ i ],那么这一类就不存在了,当然我们可以都放在这里,不存在的到时候判断一下不管就行,存在的取个max就行。
那么存在的的怎么求?其实很简单,当你进行到第 i 个数的时候,前面(i-1)的状态肯定已经求过了,对于f [ i ]来说,就是 f [ j ] + 1(这个1就是加上a [ i ]本身),然后对 f [ i ] 和 (f [ j ] + 1)取个max即可,时间复杂度是O(n²),最后遍历 f 数组找到最大值即可,因为我们不知道以谁结尾会是最大值。
这是LIS的朴素做法,它还有一个二分优化的版本,可以进一步降低时间复杂度。下面先贴朴素做法的代码。(二分优化我也还没彻底搞懂 )
代码
#include<bits/stdc++.h>
using namespace std;
int a[1005];
int f[1005];
int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
{
f[i]=max(f[i],f[j]+1);
}
}
}
int res=0;
for(int i=1;i<=n;i++) res=max(res,f[i]);
printf("%d\n",res);
return 0;
}