【转载】最长上升子序列(nlogn级)

1、线段树优化
    回顾一下最长上升子序列(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 - 梁佩琪的博客
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值