求最长上升子序列的三种经典方案:

题型简介:

给定一个长度为 $ 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 $ 的最大值。



$ solution\quad 1: $

先说一种最普遍的方法,因为所求为子序列,所以这道题很容易想到一种线性动态规划。我们需要求最长上升子序列,为了上升我们肯定要知道我们当前阶段最后一个元素为多少,为了最长我们还要知道当前我们的序列有多长。我们可以用前者来充当第一维描述:设 $ F[i] $ 表示以 $ A[i] $ 为结尾的最长上身子序列的长度,为了保证保证元素单调递增我们肯定只能从 $ i $ 前面的且末尾元素比 $ A[i] $ 小的状态转移过来:

$ F[i]=^{max}_{0\leq j<i,A[j]<a[i]}\{F[j]+1\} $

初始值为 $ F[0]=0 $ ,而答案可以是任何阶段中只要长度最长的那一个,所以我们边转移边统计答案。

复杂度: $ O(n^2) $

$ code\quad 1: $

#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;
}

$ solution\quad 2: $

我们发现上一种方法会枚举前面较小的位置,我们考虑能否用数据结构优化,首先将转移方程列一下:

$ F[i]=^{max}_{0\leq j<i,A[j]<a[i]}\{F[j]+1\} $

我们发现大括号中的 $ 1 $ 与 $ j $ 没有任何关系,所以我们将它提取出来:

$ F[i]=1+~~^{max}_{0\leq j<i,A[j]<a[i]}\{F[j]\} $

然后我们发现我们只需要将比 $ i $ 小的所有的符合 $ A[j]<A[i] $ 的 $ F[j] $ 的最大值求出来,但是这个条件 $ A[j]<A[i] $ 实在是太麻烦了,所以我们换一种思维方法:对于原序列每个元素,它有一个下标和一个权值,最长上升子序列实质就是求最多有多少元素它们的下标和权值都单调递增。

于是我们将 $ A $ 数组的每一个元素先记下他现在的下标,然后按照权值从小到大排序。接着我们按从小到大的顺序枚举 $ A $ 数组,(此时权值已经默认单调递增了)我们的转移也就变成从之前的标号比它小的状态转移过来,这个我们只需要建立一个与编号为下标维护长度的最大值的树状数组即可,枚举 $ A $ 数组时按元素的序号找到它之前序号比他小的长度最大的状态更新,然后将它也加入树状数组中。 期望复杂度: $ O(nlog(n)) $

$ code\quad 2: $

#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;
}

关于树状数组求最长上升子序列的方案及方案数,这个需要结构体来实现,构建结构体数组使其中每一个元素可以包含多个信息,这样在树状数组更新时可以做到顺便兼顾记录前驱,以及累计方案数(需要去重)。关于具体如何实现,可以参见我出的这场考试中的第二题:五彩棒

另外真的很抱歉,博主现在拿的平板,家里电脑坏了,不能具体解答。


$ solution\quad 3: $

这是最快的方法:贪心加二分查找

我之前说过:我们肯定要知道我们当前阶段最后一个元素为多少,还有当前我们的序列有多长。前两种方法都是用前者做状态,我们为什么不可以用后做状态呢?:设 $ F[i] $ 表示长度为 $ i $ 的最长上升子序列的末尾元素的最小值,我们发现这个数组的权值一定单调不降(仔细想一想,这就是我们贪心的来由)。于是我们按顺序枚举数组 $ A $ ,每一次对 $ F[] $ 数组二分查找,找到小于 $ A[i] $ 的最大的 $ F[j] $ ,并用它将 $ F[j+1] $ 更新。

注意:这个方法虽快,但是讲实话还是树状数组好一些,因为对于最长上升子序列的方案输出和计算方案数(upd:很抱歉咕掉了,现在补一下坑,在上面第二种方法结尾),树状数组有很多优势!二分查找因为贪心的缘故会被限制。

期望复杂度: $ O(nlogn) $

$ code\quad 3: $

#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;
}

LIS(最长上升子序列)的三种经典求法的更多相关文章

  1. 算法设计 - LCS 最长公共子序列&&最长公共子串 &&LIS 最长递增子序列

    出处 http://segmentfault.com/blog/exploring/ 本章讲解:1. LCS(最长公共子序列)O(n^2)的时间复杂度,O(n^2)的空间复杂度:2. 与之类似但不同的 ...

  2. POJ - 3903 Stock Exchange(LIS最长上升子序列问题)

    E - LIS Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u   Descripti ...

  3. hdu 5256 序列变换(LIS最长上升子序列)

    Problem Description 我们有一个数列A1,A2...An,你现在要求修改数量最少的元素,使得这个数列严格递增.其中无论是修改前还是修改后,每个元素都必须是整数. 请输出最少需要修改多 ...

  4. POJ 3903 Stock Exchange (E - LIS 最长上升子序列)

    POJ 3903    Stock Exchange  (E - LIS 最长上升子序列) 题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action ...

  5. 动态规划模板1|LIS最长上升子序列

    LIS最长上升子序列 dp[i]保存的是当前到下标为止的最长上升子序列的长度. 模板代码: int dp[MAX_N], a[MAX_N], n; int ans = 0; // 保存最大值 for ...

  6. POJ 1887 Testingthe CATCHER (LIS:最长下降子序列)

    POJ 1887Testingthe CATCHER (LIS:最长下降子序列) http://poj.org/problem?id=3903 题意: 给你一个长度为n (n<=200000) ...

  7. LIS最长上升子序列三种方法 (模板)

    O(n^)的方法: #include <iostream> #include <stdio.h> #include <cstring> #include <a ...

  8. 【noi 2.6_1759】LIS 最长上升子序列(DP,3种解法)

    题意我就不写了.解法有3种: 1.O(n^2).2重循环枚举 i 和 j,f[i]表示前 i 位必选 a[i] 的最长上升子序列长度,枚举a[j]为当前 LIS 中的前一个数. 1 #include& ...

  9. LIS(最长上升子序列)的 DP 与 (贪心+二分) 两种解法

    正好训练赛来了一道最长递减序列问题,所以好好研究了一下最长递增序列问题. B - Testing the CATCHER Time Limit:1000MS     Memory Limit:3000 ...

随机推荐

  1. 【Luogu】P1578奶牛浴场(DP,枚举)

    题目链接 枚举极大子矩形.详情请见本题题解:I_AM_HelloWord 代码如下 #include<cstdio> #include<cctype> #include< ...

  2. BZOJ2245 [SDOI2011]工作安排 【费用流】

    题目 你的公司接到了一批订单.订单要求你的公司提供n类产品,产品被编号为1~n,其中第i类产品共需要Ci件.公司共有m名员工,员工被编号为1~m员工能够制造的产品种类有所区别.一件产品必须完整地由一名 ...

  3. POJ 2185 Milking Grid [二维KMP next数组]

    传送门 直接转田神的了: Milking Grid Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 6665   Accept ...

  4. android中自定义下拉框(转)

    android自带的下拉框好用不?我觉得有时候好用,有时候难有,项目规定这样的效果,自带的控件实现不了,那么只有我们自己来老老实实滴写一个新的了,其实最基本的下拉框就像一些资料填写时,点击的时候出现在 ...

  5. hdu 3594 Cactus /uva 10510 仙人掌图判定

    仙人掌图(有向):同时满足:1强连通:2任何边不在俩个环中. 个人理解:其实就是环之间相连,两两只有一个公共点,(其实可以缩块),那个公共点是割点.HDU数据弱,网上很多错误代码和解法也可以过. 个人 ...

  6. 大话Spark(3)-一图深入理解WordCount程序在Spark中的执行过程

    本文以WordCount为例, 画图说明spark程序的执行过程 WordCount就是统计一段数据中每个单词出现的次数, 例如hello spark hello you 这段文本中hello出现2次 ...

  7. T1077 多源最短路 codevs

    http://codevs.cn/problem/1077/ 时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题目描述 Description 已知n个点(n&l ...

  8. hzwer与逆序对

    codevs——4163 hzwer与逆序对 貌似这个题和上个题是一样的((⊙o⊙)…)  时间限制: 1 s  空间限制: 256000 KB  题目等级 : 黄金 Gold 题解       题目 ...

  9. Java面试题总结之数据结构、算法和计算机基础(刘小牛和丝音的爱情故事1)

      Java面试题总结之数据结构.算法和计算机基础(刘小牛和丝音的爱情故事1)​mp.weixin.qq.com 全文字数: 1703 阅读时间: 大约6 分钟 刘小牛是一名Java程序员,由于天天9 ...

  10. 网络安全(超级详细)零基础带你一步一步走进缓冲区溢出漏洞和shellcode编写!

    零基础带你走进缓冲区溢出,编写shellcode. 写在前面的话:本人是以一个零基础者角度来带着大家去理解缓冲区溢出漏洞,当然如果你是开发者更好. 注:如果有转载请注明出处!创作不易.谢谢合作. 0. ...