目录

  • 最长上升子序列
    • 一、朴素做法O(2n)O(2^n)O(2n)
    • 二、优化做法O(nlogn)O(nlogn)O(nlogn)
    • 三、例题引入:P1020 导弹拦截(求最长上升子序列和最长不上升子序列)
      • 1.朴素做法暴力O(n2)O(n^2)O(n2)
      • 2.树状数组优化O(nlogn)O(nlogn)O(nlogn)
      • 3.二分优化O(nlogn)O(nlogn)O(nlogn)
    • 四、P4309 [TJOI2013]最长上升子序列

最长上升子序列

给出一个⻓度为n的数字序列ai{ai}ai,我们需要找到一个最⻓的子序列, 使得子序列中的数字大小单调递增。 例如:n=5n = 5n=5,原序列为(1,4,2,3,5)(1,4,2,3,5)(1,4,2,3,5),那么我们选择(1,2,3,5)(1,2,3,5)(1,2,3,5)是最优的。

一、朴素做法O(2n)O(2^n)O(2n)

容易想到的朴素做法是枚举所有合法的子序列,计算最⻓的⻓度。用搜 索来实现,从前往后,判断每个数字是否大于当前的序列头部的数字, 然后枚举选或不选,分别递归。复杂度O(2n)O(2^n)O(2n)。 如何优化呢?我们回忆:dp的核心思想是减少重复状态的计算。在搜索 的过程中,我们重复计算了大量相同的前缀子序列,如果能够将它们的 答案记录下来,就可以大大减少复杂度。 同时注意到:如果一个序列的头部已经确定了,那么就不会受之后选择 的数字的影响,从而有无后效性和子问题最优性。

设f(i)f(i)f(i)表示以aia_iai​作为头部(序列中最后一个)的LIS的⻓度。由于ai 可以接在所有比它小的数字后面,我们得到转移方程

f(i)=maxj<i,aj<ai{f(j)}+1f(i) = max _{ j<i,a_j<a_i} \{f(j)\}+ 1f(i)=maxj<i,aj​<ai​​{f(j)}+1

初始条件为 f(0)=0f(0) = 0f(0)=0

二、优化做法O(nlogn)O(nlogn)O(nlogn)

注意到在转移的过程中,我们需要反复地从1∼i1 ∼i1∼i中寻找满足aj<aia_j < a_iaj​<ai​的 j,并找到最大的f(j)f(j)f(j)。这一过程可以被抽象为从区间中寻找最大值的问 题。 于是,我们以数字的值为下标,建立线段树(树状数组),每次计算f(i) 时直接从1∼ai−11 ∼a_{i−1}1∼ai−1​的区间中找到最大值,然后用f(i)f(i)f(i)的值来更新线段 树中以aia_iai​为下标的值。 结合离散化,复杂度可以优化为O(nlogn)O(nlogn)O(nlogn)。

没看懂的话我再解释一下优化的策略,这里根据转移方程我们需要满足的两个条件是j<i和aj<aij<i和a_j<a_ij<i和aj​<ai​,所以用树状数组来维护这两个条件,首先树状数组维护最大值,满足了aj<aia_j<a_iaj​<ai​,然后再正序循环,先用树状数组查询当前 i 前面的最大值,再更新,满足了 j<ij<ij<i,将整个问题的时间复杂度降低到O(nlogn)O(nlogn)O(nlogn)。

当然除了可以用树状数组来压缩到O(nlogn)O(nlogn)O(nlogn),也可以用二分来做。

下面这道题目我将给出三种做法及超详细注释的代码:朴素的O(2n)O(2^n)O(2n)、树状数组的O(nlogn)O(nlogn)O(nlogn)和二分的O(nlogn)O(nlogn)O(nlogn)。相信您一看代码就懂了

三、例题引入:P1020 导弹拦截(求最长上升子序列和最长不上升子序列)

洛谷上的导弹拦截

第一问:最长不上升序列;第二问:最长上升序列;

具体的证明过程比较麻烦我不会 ,其实只需要自己算一组数据,把样例的数据写下来自己模拟一遍就很清楚了。

1.朴素做法暴力O(n2)O(n^2)O(n2)

第一问是求最长不上升子序列,利用dp的思想,我们设 f[i]f[i]f[i] 为以第 iii 个数为开头的最长不上升子序列的长度,需要倒序。

第二问是求最长上升子序列,利用dp的思想,我们设 f[i]f[i]f[i] 为以第 iii 个数为结尾的最长上升子序列的长度,需要正序。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<math.h>
#include<vector>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r)/2
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=100007;
const ll INF=1e10+9;
const ll mod=2147483647;
const double EPS=1e-10;//-10次方约等于趋近为0
const double Pi=3.1415926535897;ll n,m,ans1,ans2,f[N],a[N];int main()
{while(scanf("%lld",&a[++n])!=EOF);//先输入数据,注意最后一个输入非法才结束,n会多1n--;//所以要--;/*最长不上升子序列*///因为要求的是不上升子序列,所以后一个必须小于等于前一个,所以先求前面的再更新后面的for(int i=n;i>=1;--i)//因为它是以i开头的最长不上升子序列,所以这里要从n开始循环,必须倒序{f[i]=1;//以第i个数开头的最长不上升子序列的长度至少为1(它本身)for(int j=i+1;j<=n;++j)if(a[j]<=a[i])//不上升,所以后面的要小于等于前面的数f[i]=max(f[i],f[j]+1);ans1=max(ans1,f[i]);}/*最长上升子序列*/for(int i=1;i<=n;++i)//同上,因为它是以i结尾的最长上升子序列,所以这里要从前往后,正序前面的数推后面的数{f[i]=1;for(int j=1;j<i;++j)if(a[j]<a[i])//上升,所以前面的数要小于后面的数f[i]=max(f[i],f[j]+1);//记得一定要+1ans2=max(ans2,f[i]);}printf("%lld\n%lld\n",ans1,ans2);return 0;
}

2.树状数组优化O(nlogn)O(nlogn)O(nlogn)

要注意

最长上升子序列是不能等于的,后面要大于前面,所以要正序,并且查询的时候要-1来保证只会小于不会等于

最长不上升子序列是可以等于的,其实就是求小于等于,所以后面的要小于等于前面的,由于树状数组维护的是最大值(大于等于),而这里要的是小于等于,所以要倒序。

要注意这里要用maxn也就是a数组里的最大值来作为树状数组的上限

因为树状数组里存的就是a数组的值,并维护其最大值

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<math.h>
#include<vector>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r)/2
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=100007;
const ll INF=1e10+9;
const ll mod=2147483647;
const double EPS=1e-10;//-10次方约等于趋近为0
const double Pi=3.1415926535897;ll n,m,ans1,ans2,tree[N],a[N],maxn;
inline void update(ll k,ll val)//更新
{while(k<=maxn)//要注意这里是maxn也就是a数组里的最大值来作为树状数组的上限{tree[k]=max(tree[k],val);//维护最大值k+=k&(-k);}
}
inline ll query(ll k)
{ll res=0;while(k){res=max(res,tree[k]);//求以小于等于x的数为结尾的最长不上升子序列的长度的最大值k-=k&(-k);}return res;
}
int main()
{while(scanf("%lld",&a[++n])!=EOF)maxn=max(maxn,a[n]);n--;for(int i=n;i>=1;--i){ll q=query(a[i])+1;/*相当于朴素做法的:for(int j=i+1;j<=n;j++)if(a[j]<=a[i])因为是按照顺序从后往前循环,所以当前的i所query到的所有的值都是在i后面的,解决了i<j的问题,树状数组维护最大值,就解决了a[j]<=a[i]的问题,所以这里求的就是以i为开头的最长不上升子序列的长度*/update(a[i],q);//这个最大值+1就是以当前这个数开头的最长不上升子序列的长度,丢到树状数组里面去更新后面的值ans1=max(ans1,q);}memset(tree,0,sizeof tree);for(int i=1;i<=n;++i){ll q=query(a[i]-1)+1;/*查询以小于(没有等于!!!)x的数为结尾的最长上升子序列的长度的最大值因为不能等于所以要-1*/update(a[i],q);//这个最大值+1就是以当前这个数结尾的最长上升子序列的长度,丢到树状数组里面去ans2=max(ans2,q);}printf("%lld\n%lld\n",ans1,ans2);return 0;}

3.二分优化O(nlogn)O(nlogn)O(nlogn)

二分优化的详解

大体的思路就是数组维护一个伪单调栈,每次新的元素如果满足单调性就加入,如果不满足单调性就替换到满足的那一位置,前面的并不删除,只是替换,所以是伪单调栈,最后求最大值即可(主要是替换之后当前的答案也不会改变,但是利用了一个贪心的思想,换完之后可能会更优。因为同样是这么多,我们选更低的肯定后面选择空间会更大,比如当前最大值为300,后面还有两个数,200和250,如果我们到200的时候替换了(此时答案不变),把300替换成200,最后的250就可以取了,答案最优,不然的话250就取不了了)

#include<iostream>
#include<cstdio>
#include<algorithm>
#define R register
using namespace std;
const int N=100010;
int a[N],d1[N],d2[N],n;
inline bool read(int &x) {char c=getchar();if(c==EOF)return false;while(c>'9'||c<'0')c=getchar();while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}return true;
}
int main() {while(read(a[++n]));n--;R int len1=1,len2=1;d1[1]=d2[1]=a[1];for(R int i=2; i<=n; i++) {if(d1[len1]>=a[i])d1[++len1]=a[i];else *upper_bound(d1+1,d1+1+len1,a[i],greater<int>())=a[i];if(d2[len2]<a[i])d2[++len2]=a[i];else *lower_bound(d2+1,d2+1+len2,a[i])=a[i];}printf("%d\n%d",len1,len2);return 0;
}

四、P4309 [TJOI2013]最长上升子序列



考虑每新加进来一个数, 要么答案还是之前的, 要么这个数重新更新一个

发现数是按顺序插入的, 也就是说一个数无论放在哪里, 它后面的数对它都没
有影响

于是我们可以用平衡树之类的东西先把原序列搞出来

然后按位置插入当前位置的值, 更新当前的DP值

然后发现这里可以直接用vector代替平衡树

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<math.h>
#include<vector>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r)/2
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=100007;
const ll INF=1e10+9;
const ll mod=2147483647;
const double EPS=1e-10;//-10次方约等于趋近为0
const double Pi=3.1415926535897;
ll n,m;
vector<ll>v;
ll ans[N],tree[N];
inline void update(ll k,ll val)//维护最大值的树状数组,解决LIS问题
{while(k<=N){tree[k]=max(tree[k],val);k+=k&(-k);}
}inline ll query(ll k)
{ll res=0;while(k){res=max(tree[k],res);k-=k&(-k);}return res;
}
int main()
{scanf("%lld",&n);ll t;over(i,1,n){scanf("%lld",&t);v.insert(t+v.begin(),i);}ll now;over(i,0,n-1){now=v[i];ans[now]=query(now)+1;update(now,ans[now]);}over(i,1,n){ans[i]=max(ans[i],ans[i-1]);printf("%lld\n",ans[i]);}return 0;
}

注:如果您通过本文,有(qi)用(guai)的知识增加了,请您点个赞再离开,如果不嫌弃的话,点个关注再走吧,日更博主每天在线答疑 ! 当然,也非常欢迎您能在讨论区指出此文的不足处,作者会及时对文章加以修正 !如果有任何问题,欢迎评论,非常乐意为您解答!( •̀ ω •́ )✧

最长上升子序列(LIS)/最长不上升子序列问题算法详解+例题(树状数组/二分优化,看不懂你来打我)相关推荐

  1. 最长公共子序列(LCS)问题算法详解+例题(转换成LIS,优化为O(nlogn),看不懂你来打我)

    目录 最长公共子序列(LCS)问题 1.朴素做法 O(n2)O(n^2)O(n2) 2.转换成LIS优化O(nlogn)O(nlogn)O(nlogn) 3.P2758 编辑距离 最长公共子序列(LC ...

  2. 求序列最长不下降子序列_树状数组解决最长不下降子序列 讲讲主要思路就好...

    展开全部 不降子序列求的是一个元素的值单调e69da5e887aa62616964757a686964616f31333361306430不降的序列. 传统的状态设计便是使用f[n] 表示到达第n位时 ...

  3. 最长上升子序列 (LIS) 详解+例题模板 (全)

    欢迎访问https://blog.csdn.net/lxt_Lucia-- 宇宙第一小仙女\(^o^)/-萌量爆表求带飞=≡Σ((( つ^o^)つ~ dalao们点个关注呗- ------------ ...

  4. LIS最长上升子序列详解(动态规划、贪心+二分、树状数组)

    1.摘要: 关于LIS部分,本篇博客讲一下LIS的概念定义和理解,以及求LIS的三种方法,分别是O(n^2)的DP,O(nlogn)的二分+贪心法,以及O(nlogn)的树状数组优化的DP,最后附上几 ...

  5. 最长上升子序列三种模板(n^2模板,二分模板,树状数组模板)

    最长上升子序列(LIS)是动态规划的入门.总结下来,经常用的模板一共有三种,分别为n^2模板,二分模板,树状数组模板. n^2模板代码如下: //n^2算法,本质就是dp,采用二重循环的方式.对于数据 ...

  6. 【BZOJ】3173: [Tjoi2013]最长上升子序列(树状数组)

    [题意]给定ai,将1~n从小到大插入到第ai个数字之后,求每次插入后的LIS长度. [算法]树状数组||平衡树 [题解] 这是树状数组的一个用法:O(n log n)寻找前缀和为k的最小位置.(当数 ...

  7. bzoj 3173: [Tjoi2013]最长上升子序列(离线二分+树状数组)

    3173: [Tjoi2013]最长上升子序列 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 2051  Solved: 1041 [Submit] ...

  8. Gym - 101889F Fundraising(树状数组求带权最长上升子序列)

    ICPC Latin American Regional – 2017 Problem F – Fundraising Author: Paulo Cezar Pereira Costa, Brasi ...

  9. 20200118:(leetcode)最长回文子串(中心扩展算法详解及思考)

    最长回文子串(中心扩展算法详解及思考) 题目 中心扩展算法详解 代码实现 题目 给定一个字符串 s,找到 s 中最长的回文子串.你可以假设 s 的最大长度为 1000. 示例 1: 输入: " ...

最新文章

  1. Linux 中echo格式控制、重定向 、管道 | 简介
  2. 基于大数据审计的信息安全日志分析法
  3. (转)Git详解之一:Git起步
  4. 自动构建工具Grunt
  5. React 的 PureComponent Vs Component
  6. 垃圾回收算法简单介绍——JVM读书笔记lt;二gt;
  7. matplotlib学习记录 四
  8. mysql binlog mixed_MySQL Binlog三种格式介绍及分析
  9. spss正态性检验_SPSS和R中的正态分布的确定和几何均值的计算
  10. android文件管理器--界面效果二(layout)
  11. 自然语言处理——语言模型(一)
  12. c语言单片机程序设计例,单片机 C语言 程序设计100例
  13. 【边喝caffee边Caffe 】(二)python版本的报错:Mean shape incompatible with input shape
  14. Lucene和Solr原理初探
  15. 电商产品竞品分析报告(聚水潭良品铺子)
  16. 更新一波,特殊福利 !
  17. 淘宝钻石(信用)的等级
  18. dp算法求解矩阵连乘的问题
  19. 信息学奥赛一本通1005:地球人口承载力估计
  20. 轻快步伐远不足以跟上轻快心情

热门文章

  1. 推特雪花算法 java实现
  2. 今日宇宙最热科技:人工智能可预测死亡时间,马斯克拿下美空军1.3亿合同!...
  3. vue 时间安排表
  4. 多系统交互中DBA该确认的一些事情
  5. shell 监控局域网的主机是否up(转)
  6. 陶哲轩实分析 习题 7.5.2
  7. linux 学习笔记 (1) —— 安装 Redhat enterprise 5
  8. Java类和对象的初始化顺序
  9. 如何建立顺畅的项目流程
  10. Python九十天学习框架,从1到90,从0基础到IQ100