有三种解法 n^2 和两个n*log(n) 一个讲得很好的博客:地址
n^2的,动态规划:
动归问题,首先很容易认为这道题的子问题就是,求前n个数字序列的解,这样的错误之处在于,“状态具有后效性”,前n个数字序列的解可能有多种,每一种可能结尾数字不一样,这样对后面的解就有了影响(后效性)
比较好的子问题是,求以第n个数为结尾的数字序列的解,这样的子问题,便没有了后效性
事实上,我认为第一种子问题如果加以处理也能消除后效性,但是我思考了一下觉得这样子会很麻烦,有兴趣的同学可以试试,一起讨论一下~
确定了子问题剩下的就很简单
ac代码:
#include<iostream> #include<cstring> #include<queue> #include<algorithm> using namespace std; #define maxn 1000+10 #define inf 1<<31 int dp[maxn],num[maxn]; int main(){ int n,ans=-1; cin>>n; for(int i=1;i<=n;i++){ cin>>num[i];dp[i]=1; for(int j=1;j<i;j++){ if(num[j]<num[i]){ dp[i]=max(dp[i],dp[j]+1); } } } cout<<*max_element(dp+1,dp+n+1); return 0; }
n*log(n)的,贪心+二分:
新建一个low数组,low[i]表示长度为i的LIS结尾元素的最小值。对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。因此,我们只需要维护low数组,对于每一个a[i],如果a[i] > low[当前最长的LIS长度],就把a[i]接到当前最长的LIS后面,即low[++当前最长的LIS长度]=a[i]。
那么,怎么维护low数组呢?
对于每一个a[i],如果a[i]能接到LIS后面,就接上去;否则,就用a[i]取更新low数组。具体方法是,在low数组中找到第一个大于等于a[i]的元素low[j],用a[i]去更新low[j]。如果从头到尾扫一遍low数组的话,时间复杂度仍是O(n^2)。我们注意到low数组内部一定是单调不降的,所有我们可以二分low数组,找出第一个大于等于a[i]的元素。二分一次low数组的时间复杂度的O(lgn),所以总的时间复杂度是O(nlogn)。
ac代码:
#include<cstdio> #include<algorithm> #define maxn 1000+10 int lower[maxn],num[maxn]; int main(){ int n; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&num[i]); } int ans=1; lower[1]=num[1]; for(int j=2;j<=n;j++){ if(num[j]>lower[ans]){ lower[++ans]=num[j]; } else{ int l=std::lower_bound(lower+1,lower+ans,num[j])-lower; lower[l]=num[j]; } } printf("%d ",ans); return 0; }
最后这种方法看起来比较麻烦,用到了树状数组,我还没有学到,篮桥北貌似也不考这个,因此以后在更吧~