题目描述:
- 又有一长为n的数列:a0,a1,…,an−1a_0,a_1,…,a_{n-1}a0,a1,…,an−1.求出这个序列中最长的上升子序列长度。上升子序列指的是对于任意i<ji<ji<j都满足ai<aja_i<a_jai<aj。
限制条件:
- i≤n≤1000i≤n≤1000i≤n≤1000
- 0≤ai≤10000000≤a_i≤10000000≤ai≤1000000
题解:
动态规划:
- 步骤:
- dp数组含义:dp[i]dp[i]dp[i]=以aia_iai为结尾的最长上升子序列的长度。
- 初始条件:dp[0]dp[0]dp[0]不使用 dp[1]=1dp[1]=1dp[1]=1
- 递推公式:dp[i]=max{1,dp[j]+1∣j<i且aj<ai}dp[i]=max\{1,dp[j]+1|j<i且a_j<a_i\}dp[i]=max{1,dp[j]+1∣j<i且aj<ai}
- 结果:max{dp[i]∣1<=i<=n}max\{dp[i]|1<=i<=n\}max{dp[i]∣1<=i<=n}
- 时间复杂度 :O(n2)O(n^2)O(n2)
- 代码 :函数 solve1()solve1()solve1()
优化算法:
- 根据上面dpdpdp数组的含义,我们知道上面dpdpdp数组主要记录了两个信息:iii表示ai,dpa_i,dpai,dp数组里面存的是长度。我要优化该算法就必须从这个两个方向入手。我们知道在长度相同的上升序列中末尾元素越小越好,基于这个想法我们得到下面的动态规划:
- 步骤:
- dp数组含义:dp[i]dp[i]dp[i]=长度为i的上升子序列中末尾元素的最小值,如果长度i不存在就记为infinfinf(无穷大)
- 初始化:dp[0]dp[0]dp[0]不使用, dp[1-n]=inf
- 递推公式:循环整个数列的每个元素aia_iai,对每个元素aia_iai循环dpdpdp数组,找到第一个大于aia_iai的dp[j]dp[j]dp[j],更新dp[j]=minai,dp[j]dp[j]=min{a_i,dp[j]}dp[j]=minai,dp[j]
- 时间复杂度:O(n2)O(n^2)O(n2)
- 可以进一步优化,因为对于每个aia_iai循环dpdpdp数组时dpdpdp数组时单调递增的,所有我们查找dp[j]dp[j]dp[j]可以使用二分查找法,这样时间复杂度变为: O(nlog2n)O(nlog_2 n)O(nlog2n)
- 结果:最大的不为inf的dp数组下标。
- 代码 :函数 solve2()solve2()solve2()
代码:
#include <iostream>
#define Max_N 1005
#define Inf 1000000
using namespace std;
int n;
int a[Max_N];
int dp[Max_N];
void solve1()
{
dp[1]=1;
for(int i=2; i<=n; i++)
{
dp[i]=1;
for(int j=i-1;j>=1;j--)
if (a[i]>a[j])
dp[i]=max(dp[i],dp[j]+1);
}
int res=-1;
for(int i=1;i<=n;i++)
if(res<dp[i])
res=dp[i];
cout<<res<<endl;
}
void solve2()
{
for(int i=1; i<=n; i++)
dp[i]=Inf;
for(int i=1;i<=n;i++)
*lower_bound(dp+1,dp+n+1,a[i])=a[i];
cout<<(lower_bound(dp+1,dp+n+1,Inf)-dp-1)<<endl;
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
solve1();
solve2();
return 0;
}
二分查找函数:
- lower_bound(first, last,val)算法返回一个非递减序列[first, last)中的第一个大于等于值val的位置(指针)。
- upper_bound(first, last,val)算法返回一个非递减序列[first, last)中的第一个大于值val的位置(指针)