回顾一下最长上升子序列(http://liangpeiqi0.blog.163.com/blog/static/24671503920157611053800/),用f[i]来记录以i为结尾的子序列里面包含的最长上升子序列的数字个数。那么f[i]可以接上前面任何一个比自己小的数,所以就可以求出DP方程:f[i] = max{f[j] + 1,f[i]} 其中j < i && a[j] < a[i] 。那么求最大值就是线段树最简单的应用就不说了。要满足j < i可以每处理一个数就记录在线段树里,那么a[i]后面的数因还没处理,就不用考虑了。而要满足a[j] < a[i],就要预处理一下,把a数组离散化,也就是排个序,排序后的a数组就是线段树里叶子结点顺序,要找比a[i]小的数,就在a[i]前面找就可以了。
# include <iostream> # include <stdio.h> # include <algorithm>
using namespace std; int n,fp=0; const int maxN=100000; struct data { int num,h; }; data a[maxN],b[maxN]; struct Tnode { int l,r,max,lc,rc; }t[maxN*5];
int getp(int l,int r) { fp++; t[fp].l=l; t[fp].r=r; t[fp].max=0; return fp; } // 申请结点
int build(int l,int r) { int now=getp(l,r); if (l<r) { t[now].lc=build(l,(l+r)/2); t[now].rc=build((l+r)/2+1,r); } return now; } // 建树
bool cmp(data a,data b) { return a.num<b.num; }
int findmax(int root,int l,int r) { if (t[root].l>r || t[root].r<l) return 0; if (l<=t[root].l && r>=t[root].r) return t[root].max; return max( findmax(t[root].lc,l,r), findmax(t[root].rc,l,r) ); }
void putin(int root,int x,int c) { if (x<t[root].l || x>t[root].r) return; if (t[root].l==t[root].r) { t[root].max=c; return; } putin(t[root].lc,x,c); putin(t[root].rc,x,c); t[root].max=max(t[t[root].lc].max,t[t[root].rc].max); }
int main() { cin>>n; for (int i=0; i<n; i++) { cin>>a[i].num; b[i].num=a[i].num; b[i].h=i; } sort(b,b+n,cmp); for (int i=0; i<n; i++) a[b[i].h].h=i+1; // 离散化 int root=build(1,n); int ans=0,f[maxN]; for (int i=0; i<n; i++) { f[i]=findmax(root,1,a[i].h-1)+1; // 在前面找满足条件的最大值 putin(root,a[i].h,f[i]); // 添加 ans=max(f[i],ans); } cout<<ans<<endl; return 0; }
2、二分 这个算法写起来比较简单,特别是用C++。用f[i]表示长度为i的最后一个数的最小值。其中有个性质——当i<j时,f[i]<f[j]。这个其实蛮好理解,f[j]一定要接上f[j-1]的长度,那么f[j]>f[j-1]是绝对的,以此类推,f[j]<f[i]也是绝对的。因此f数组具有单调性,可以用二分找出最大值了。
# include <iostream> # include <stdio.h> # include <cstdlib>
using namespace std; const int maxN=1000005,oo=1000000; int a[maxN],f[maxN],n;
int main() { cin>>n; for (int i=0; i<n; i++) cin>>a[i]; for (int i=0; i<n; i++) f[i+1]=oo; f[0]=-oo; int ans=0; for (int i=0; i<n; i++) { int p=lower_bound(0,i,a[i]); f[p]=min(f[p],a[i]); ans=max(ans,p); } cout<<ans<<endl; return 0; }
lower_bound(first,last,val)算法返回一个非递减序列[first, last)中的第一个大于等于值val的位置。
upper_bound(first,last,val)算法返回一个非递减序列[first, last)中第一个大于val的位置。
lower_bound和upper_bound如下图所示:
![最长上升子序列(nlogn级) - liangpeiqi0 - 梁佩琪的博客 最长上升子序列(nlogn级) - liangpeiqi0 - 梁佩琪的博客](http://img0.ph.126.net/8_XJoeAw8RvApHrRm3PzDQ==/6631348141168666038.png)