给定一个长度为 $ N $ 的数列,求它数值单调递增的子序列长度最大为多少。即已知有数列 $ A $ , $ A={A_1,A_2....A_n} $ ,求 $ A $ 的任意子序列 $ B $ ( $ B={A_{k_1},A_{k_2}....A_{k_p}} $ ),使 $ B $ 知足 $ k_1<k_2<....<k_p $ 且 $ A_{k_1}<A_{k_2}<....<A_{k_p} $ 。现求 $ p $ 的最大值。html
先说一种最广泛的方法,由于所求为子序列,因此这道题很容易想到一种线性动态规划。咱们须要求最长上升子序列,为了上升咱们确定要知道咱们当前阶段最后一个元素为多少,为了最长咱们还要知道当前咱们的序列有多长。咱们能够用前者来充当第一维描述:设 $ F[i] $ 表示以 $ A[i] $ 为结尾的最长上身子序列的长度,为了保证保证元素单调递增咱们确定只能从 $ i $ 前面的且末尾元素比 $ A[i] $ 小的状态转移过来:ios
初始值为 $ F[0]=0 $ ,而答案能够是任何阶段中只要长度最长的那一个,因此咱们边转移边统计答案。算法
复杂度: $ O(n^2) $数组
#include<iostream> #include<cstdio> #define ll long long #define rg register int using namespace std; int n,ans; int a[10005]; int f[10005]; int main(){ cin>>n; for(rg i=1;i<=n;++i) cin>>a[i]; for(rg i=1;i<=n;++i){ for(rg j=1;j<i;++j) //枚举转移 if(a[j]<a[i])f[i]=max(f[i],f[j]); ++f[i]; ans=max(ans,f[i]); //更新答案 }cout<<ans<<endl; return 0; }
咱们发现上一种方法会枚举前面较小的位置,咱们考虑可否用数据结构优化,首先将转移方程列一下:数据结构
咱们发现大括号中的 $ 1 $ 与 $ j $ 没有任何关系,因此咱们将它提取出来:优化
而后咱们发现咱们只须要将比 $ i $ 小的全部的符合 $ A[j]<A[i] $ 的 $ F[j] $ 的最大值求出来,可是这个条件 $ A[j]<A[i] $ 实在是太麻烦了,因此咱们换一种思惟方法:对于原序列每一个元素,它有一个下标和一个权值,最长上升子序列实质就是求最多有多少元素它们的下标和权值都单调递增。spa
因而咱们将 $ A $ 数组的每个元素先记下他如今的下标,而后按照权值从小到大排序。接着咱们按从小到大的顺序枚举 $ A $ 数组,(此时权值已经默认单调递增了)咱们的转移也就变成从以前的标号比它小的状态转移过来,这个咱们只须要创建一个与编号为下标维护长度的最大值的树状数组便可,枚举 $ A $ 数组时按元素的序号找到它以前序号比他小的长度最大的状态更新,而后将它也加入树状数组中。 指望复杂度: $ O(nlog(n)) $code
#include<iostream> #include<cstdio> #include<algorithm> #define ll long long #define rg register int using namespace std; int n; int s[200005]; struct su{ int v,id; //按照权值为第一关键字保证算法正确性 inline bool operator <(su x){ if(v==x.v)return id>x.id; //按照序号从大到小能够保证所求为上升子序列 return v<x.v; //(针对标号)由于相同权值的数,前面的状态不能转移给后面 } //(针对标号)从大到小枚举就不会出现这种状况 }a[200005]; inline void add(int x,int y){ for(;x<=n;x+=x&-x) s[x]=max(s[x],y); } inline int ask(int x){ rg res=0; for(;x>=1;x-=x&-x) res=max(s[x],res); return res; } int main(){ cin>>n; for(rg i=1;i<=n;++i) cin>>a[i].v,a[i].id=i; sort(a+1,a+n+1); for(rg i=1;i<=n;++i) add(a[i].id,ask(a[i].id)+1); cout<<ask(n)<<endl; return 0; }
关于树状数组求最长上升子序列的方案及方案数,这个须要结构体来实现,构建结构体数组使其中每个元素能够包含多个信息,这样在树状数组更新时能够作到顺便兼顾记录前驱,以及累计方案数(须要去重)。关于具体如何实现,能够参见我出的这场考试中的第二题:五彩棒htm
另外真的很抱歉,博主如今拿的平板,家里电脑坏了,不能具体解答。blog
这是最快的方法:贪心加二分查找
我以前说过:咱们确定要知道咱们当前阶段最后一个元素为多少,还有当前咱们的序列有多长。前两种方法都是用前者作状态,咱们为何不能够用后作状态呢?:设 $ F[i] $ 表示长度为 $ i $ 的最长上升子序列的末尾元素的最小值,咱们发现这个数组的权值必定单调不降(仔细想想,这就是咱们贪心的来由)。因而咱们按顺序枚举数组 $ A $ ,每一次对 $ F[] $ 数组二分查找,找到小于 $ A[i] $ 的最大的 $ F[j] $ ,并用它将 $ F[j+1] $ 更新。
注意:这个方法虽快,可是讲实话仍是树状数组好一些,由于对于最长上升子序列的方案输出和计算方案数(upd:很抱歉咕掉了,如今补一下坑,在上面第二种方法结尾),树状数组有不少优点!二分查找由于贪心的缘故会被限制。
指望复杂度: $ O(nlogn) $
#include<iostream> #include<cstdio> #include<algorithm> #define ll long long #define rg register int using namespace std; int n; int a[200005]; int f[200005]; int main(){ cin>>n; for(rg i=1;i<=n;++i) cin>>a[i]; rg ans=1; f[1]=a[1]; for(rg i=2;i<=n;++i){ rg l=1,r=ans,mid; while(l<=r){ mid=(l+r)>>1; if(a[i]<=f[mid])r=mid-1; else l=mid+1; }f[l]=a[i]; if(l>ans)++ans; }cout<<ans<<endl; return 0; }