首先介绍一下LIS和LCS的DP解法O(N^2)

LCS:两个有序序列a和b,求他们公共子序列的最大长度

我们定义一个数组DP[i][j],表示的是a的前i项和b的前j项的最大公共子序列的长度,那么由于是用迭代法,所以计算DP[i][j]前,DP[i-1][j]和DP[i][j-1]就都已经计算出来了,不难理解就可以得出状态转移方程:

DP[i][j]  = DP[i-1][j-1] + 1;   如果a[i] == b[j]

MAX(DP[i-1][j], DP[i][j-1])  如果a[i] != b[j]

LIS:一个a序列,求它的最长上升子序列的最大长度

另外,由于每次都是扫描b数组,那我们就只需要一个DP[2][B]的数组就可以了,DP[0][]依赖于DP[1][],DP[1][]依赖于DP[0][],只要实现动态处理就可以了。

这个状态转移方程也不难给出

DP[j] = MAX(DP[i]) + 1 满足条件a[j] > a[i]

对每一个a[j],枚举一遍a[0]....a[j-1]就可以了

下面再来看看他们的O(nlogn)的解法

首先是LCS,我们把a序列中的每个元素在b中出现的位置保存起来,再按照降序排列,排列后再代入a的每个对应元素,那就转化为了求这个新的序列的最长上升子序列了。如:a[] = {a, b, c,} b[] = {a,b,c,b,a,d},那么a中的a,b,c在b中出现的位置分别就是

{0,4},{1,3},{2}分别按降序排列后代入a序列就是{4,0,2,3,1},之所以要按照降序排列,目的就是为了让每个元素只取到一次。

接下来的问题就是要求最长升序子序列问题了,也就是求LIS。

PS:个人认为这种转化并没有多大意义,因为当数据比较大时,举一个例子来说:a[]= “aaaaaaaaaaa”,b[] = "aaaaaaaaaaa",他们都有n个a,那么a[]中‘a’在b[]出现了n次那么替换之后的a就变为了n-1...1,n-1...1,... ...这样a[]就有了n^2个数了这样不仅是空间,就连时间也比原来n^2要大。所以感觉这种转化还是要慎用。

下面的LIS的O(nlogn)转自于http://hi.baidu.com/fandywang_jlu/item/da673a3d83e2a65980f1a7e1

一、算法思想

算法还是容易想到的,两重循环DP即可。不过如果数据规模最大可以达到几十万甚至更大,经典的O(n^2)的动态规划算法明显会超时。我们需要寻找更好的方法来解决是最长上升子序列问题。以下以最长递增子序列为例进行说明:

   先回顾经典的O(n^2)的动态规划算法,设A[i]表示序列中的第i个数,F[i]表示从1到i这一段中以i结尾的最长上升子序列的长度,初始时设 F[i] = 0(i = 1, 2, ..., len(A))。则有动态规划方程:F[i] = max{1, F[j] + 1} (j = 1, 2, ..., i - 1, 且A[j] < A[i])。

  现在,我们仔细考虑计算F[i]时的情况。假设有两个元素A[x]和A[y],满足(1)y < x < i (2)A[x] < A[y] < A[i] (3)F[x] = F[y]

  此时,选择F[x]和选择F[y]都可以得到同样的F[i]值,那么,在最长上升子序列的这个位置中,应该选择A[x]还是应该选择A[y]呢?

  很明显,选择A[x]比选择A[y]要好。因为由于条件(2),在A[x+1] ... A[i-1]这一段中,如果存在A[z],A[x] < A[z] < A[y],则与选择A[y]相比,将会得到更长的上升子序列。

  再根据条件(3),我们会得到一个启示:根据F[]的值进行分类。对于F[]的每一个取值k,我们只需要保留满足F[i] = k的所有A[i]中的最小值。设D[k]记录这个值,即D[k] = min{ A[i] } ( F[i] = k )。

  注意到D[]的两个特点:

  (1) D[k]的值是在整个计算过程中是单调不上升的。//此处需要特别注意!!!关键之所在!

  (2) D[]的值是有序的,即D[1] < D[2] < D[3] < ... < D[n]。

利 用D[],我们可以得到另外一种计算最长上升子序列长度的方法。设当前已经求出的最长上升子序列长度为len。先判断A[i]与D[len],若A[i] > D[len],则将A[i]接在D[len]后将得到一个更长的上升子序列,len = len + 1,D[len+1] = A[i];否则,在D[1]..D[len]中,找到最大的j,满足D[j] < A[i].令k = j + 1,则有D[j] < A[i] <= D[k],将A[i]接在D[j]后将得到一个更长的上升子序列,同时更新D[k] = A[i].最后,len即为所要求的最长上升子序列的长度。

  在上述算法中,若使用朴素的顺序查找在D[1]..D[len]查找,由于 共有O(n)个元素需要计算,每次计算时的复杂度是O(n),则整个算法的时间复杂度为O(n^2),与原来的算法相比没有任何进步.但是由于D[]的特 点(2),我们在D[]中查找时,可以使用二分查找高效地完成,则整个算法的时间复杂度下降为O(nlogn),有了非常显著的提高.需要注意的 是,D[]在算法结束后记录的并不是一个符合题意的最长上升子序列.

这个算法还可以扩展到整个最长子序列系列问题,整个算法的难点在于二分查找的设计,需要非常小心注意.

二、实现

// By Fandywang 2008.7.21

// Call: LIS(a, n); 求最大递增/上升子序列(如果为最大非降子序列,只需把上面的注释部分给与替换)

const int N = 1001;

int a[N], f[N], d[N]; // d[i]用于记录a[0...i]的最大长度

int bsearch(const int *f, int size, const int &a)

{

int l=0, r=size-1;

while( l <= r )

{

int mid = (l+r)/2;

if( a > f[mid-1] && a <= f[mid] ) return mid; // >&&<= 换为: >= && <

else if( a < f[mid] ) r = mid-1;

else l = mid+1;

}

}

int LIS(const int *a, const int &n){

int i, j, size = 1;

f[0] = a[0]; d[0] = 1;

for( i=1; i < n; ++i ){

if( a[i] <= f[0] ) j = 0;                 // <= 换为: <

else if( a[i] > f[size-1] ) j = size++;   // > 换为: >=

else j = bsearch(f, size, a[i]);

f[j] = a[i]; d[i] = j+1;

}

return size;

}

三、学以致用

JOJ 1829 Candies   最大非下降子序列

JOJ 2162 Inuyasha And the Monsters 求和最大的非下降子序列 (这个我用的是O(n^2)的算法做的,居然还那个了第一...)

JOJ 2529Chorus 最大上升子序列+最大下降子序列

JOJ 1048 Wooden Sticks 先自定义排下序(先按l从小到大, l相等再按w从小到大), 然后求w的最大下降子序列

POJ 3636Nested Dolls 与JOJ1048类似 w从小到大,w相等h从大到小,然后求h最大非升子序列

至于LCIS,有了上面的比较深刻的理解,相信应该会比较好理解(转自百度文库)

最长公共上升子序列(LCIS)的O(n^2)算法

预备知识:动态规划的基本思想,LCS,LIS。

问题:字符串a,字符串b,求a和b的LCIS(最长公共上升子序列)。

首先我们可以看到,这个问题具有相当多的重叠子问题。于是我们想到用DP搞。DP的首要任务是什么?定义状态。

1定义状态F[i][j]表示以a串的前i个字符b串的前j个字符且以b[j]为结尾构成的LCIS的长度。

为什么是这个而不是其他的状态定义?最重要的原因是我只会这个,还有一个原因是我知道这个定义能搞到平方的算法。而我这只会这个的原因是,这个状态定义实在是太好用了。这一点我后面再说。

我们来考察一下这个这个状态。思考这个状态能转移到哪些状态似乎有些棘手,如果把思路逆转一下,考察这个状态的最优值依赖于哪些状态,就容易许多了。这个状态依赖于哪些状态呢?

首先,在a[i]!=b[j]的时候有F[i][j]=F[i-1][j]。为什么呢?因为F[i][j]是以b[j]为结尾的LCIS,如果F[i][j]>0那么就说明a[1]..a[i]中必然有一个字符a[k]等于b[j](如果F[i][j]等于0呢?那赋值与否都没有什么影响了)。因为a[k]!=a[i],那么a[i]对F[i][j]没有贡献,于是我们不考虑它照样能得出F[i][j]的最优值。所以在a[i]!=b[j]的情况下必然有F[i][j]=F[i-1][j]。这一点参考LCS的处理方法。

那如果a[i]==b[j]呢?首先,这个等于起码保证了长度为1的LCIS。然后我们还需要去找一个最长的且能让b[j]接在其末尾的LCIS。之前最长的LCIS在哪呢?首先我们要去找的F数组的第一维必然是i-1。因为i已经拿去和b[j]配对去了,不能用了。并且也不能是i-2,因为i-1必然比i-2更优。第二维呢?那就需要枚举b[1]..b[j-1]了,因为你不知道这里面哪个最长且哪个小于b[j]。这里还有一个问题,可不可能不配对呢?也就是在a[i]==b[j]的情况下,需不需要考虑F[i][j]=F[i-1][j]的决策呢?答案是不需要。因为如果b[j]不和a[i]配对,那就是和之前的a[1]..a[j-1]配对(假设F[i-1][j]>0,等于0不考虑),这样必然没有和a[i]配对优越。(为什么必然呢?因为b[j]和a[i]配对之后的转移是max(F[i-1][k])+1,而和之前的i`配对则是max(F[i`-1][k])+1。显然有F[i][j]>F[i`][j],i`>i)

于是我们得出了状态转移方程:

a[i]!=b[j]:   F[i][j]=F[i-1][j]

a[i]==b[j]:   F[i][j]=max(F[i-1][k])+1 1<=k<=j-1&&b[j]>b[k]

不难看到,这是一个时间复杂度为O(n^3)的DP,离平方还有一段距离。

但是,这个算法最关键的是,如果按照一个合理的递推顺序,max(F[i-1][k])的值我们可以在之前访问F[i][k]的时候通过维护更新一个max变量得到。怎么得到呢?首先递推的顺序必须是状态的第一维在外层循环,第二维在内层循环。也就是算好了F[1][len(b)]再去算F[2][1]。

如果按照这个递推顺序我们可以在每次外层循环的开始加上令一个max变量为0,然后开始内层循环。当a[i]>b[j]的时候令max=F[i-1][j]。如果循环到了a[i]==b[j]的时候,则令F[i][j]=max+1。

最后答案是F[len(a)][1]..F[len(a)][len(b)]的最大值。

参考代码:

 1 #include<cstdio>
2 #include<cstring>
3 int f[1005][1005],a[1005],b[1005],i,j,t,n1,n2,max;
4 int main()
5 {
6 scanf("%d",&t);
7 while(t--)
8 {
9 scanf("%d%d",&n1,&n2);
10 for(i=1;i<=n1;i++) scanf("%d",&a[i]);
11 for(i=1;i<=n2;i++) scanf("%d",&b[i]);
12 memset(f,0,sizeof(f));
13 for(i=1;i<=n1;i++)
14 {
15 max=0;
16 for(j=1;j<=n2;j++)
17 {
18 f[i][j]=f[i-1][j];
19 if (a[i]>b[j]&&max<f[i-1][j]) max=f[i-1][j];
20 if (a[i]==b[j]) f[i][j]=max+1;
21 }
22 }
23 max=0;
24 for(i=1;i<=n2;i++) if (max<f[n1][i]) max=f[n1][i];
25 printf("%d\n",max);
26 }
27 }

其实还有一个很风骚的一维的算法。在此基础上压掉了一维空间(时间还是平方)。i循环到x的时候,F[i]表示原来F[x][j]。之所以可以这样,是因为如果a[i]!=b[j],因为F[x][j]=F[x-1][j]值不变,F[x]不用改变,沿用过去的就好了,和这个比较维护更新得到的max值依然是我们要的。而a[i]==b[j]的时候,就改变F[x]的值好了。具体结合代码理解。

参考代码:

 1 #include<cstdio>
2 #include<cstring>
3 int f[1005],a[1005],b[1005],i,j,t,n1,n2,max;
4 int main()
5 {
6 scanf("%d",&t);
7 while(t--)
8 {
9 scanf("%d%d",&n1,&n2);
10 for(i=1;i<=n1;i++) scanf("%d",&a[i]);
11 for(i=1;i<=n2;i++) scanf("%d",&b[i]);
12 memset(f,0,sizeof(f));
13 for(i=1;i<=n1;i++)
14 {
15 max=0;
16 for(j=1;j<=n2;j++)
17 {
18 if (a[i]>b[j]&&max<f[j]) max=f[j];
19 if (a[i]==b[j]) f[j]=max+1;
20 }
21 }
22 max=0;
23 for(i=1;i<=n2;i++) if (max<f[i]) max=f[i];
24 printf("%d\n",max);
25 }
26 }

最长公共上升子序列(LCIS)的平方算法

LIS和LCS LCIS的更多相关文章

  1. LIS+LCS+LCIS

    PS:本篇博文均采用宏#define FOR(i, a, n) for(i = a; i <= n; ++i) LIS:最长上升子序列 废话不多说:http://baike.baidu.com/ ...

  2. 8.3 LIS LCS LCIS(完结了==!)

    感觉这个专题真不好捉,伤心了,慢慢啃吧,孩纸 地址http://acm.hust.edu.cn/vjudge/contest/view.action?cid=28195#overview 密码  ac ...

  3. LIS LCS LCIS (主要过一遍,重在做题)

    只详细讲解LCS和LCIS,别的不讲-做题优先. 菜鸟能力有限写不了题解,可以留评论,我给你找博客. 先得理解最长上升子序列吧,那个HDOJ拦截导弹系列可以做一下,然后用o(n)log(n)的在做一遍 ...

  4. LIS && LCS && LCIS && LPS && MCS模板

    1. LIS (Longest Increasing Subsequence) O (n^2): /* LIS(Longest Increasing Subsequence) 最长上升子序列 O (n ...

  5. LIS&&LCS&&LCIS

    LIS #include<bits/stdc++.h> using namespace std; int n,a[100005],b[100005],ji; int main(){ cin ...

  6. 线性DP总结(LIS,LCS,LCIS,最长子段和)

    做了一段时间的线性dp的题目是时候做一个总结 线性动态规划无非就是在一个数组上搞嘛, 首先看一个最简单的问题: 一,最长字段和 下面为状态转移方程 for(int i=2;i<=n;i++) { ...

  7. O(nlogn)LIS及LCS算法

    morestep学长出题,考验我们,第二题裸题但是数据范围令人无奈,考试失利之后,刻意去学习了下优化的算法 一.O(nlogn)的LIS(最长上升子序列) 设当前已经求出的最长上升子序列长度为len. ...

  8. UVa 111 History Grading (简单DP,LIS或LCS)

    题意:题意就是坑,看不大懂么,结果就做不对,如果看懂了就so easy了,给定n个事件,注意的是, 它给的是第i个事件发生在第多少位,并不是像我们想的,第i位是哪个事件,举个例子吧,4 2 3 1, ...

  9. 关于LIS和LCS问题的o(nlogn)解法

    o(n^2)解法就不赘述了,直接解释o(nlogn)解法 LIS最长递增子序列: 先明确一个结论:在长度最大为len的递增序列里若末尾元素越小,该递增序列越容易和后面的子序列构造出一个更长的递增子序列 ...

随机推荐

  1. linux(8)

    第十九单元 nfs服务 ===============服务端 介绍: NFS 是Network File System的缩写,即网络文件系统.一种使用于分散式文件系统的协定,由Sun公司开发,于198 ...

  2. C++使用回溯法实现N皇后问题的求解

    回溯法是个很无聊的死算方法,没什么技巧,写这篇博客主要原因是以前思路不太清晰,现在突然想用回溯法解决一个问题时,无法快速把思路转换成代码. ------------------------------ ...

  3. m'ybatis 一对一 一对多 配置详解

    javabean: package com.me.model; import java.io.Serializable; import java.util.Date; import java.util ...

  4. MongoDB在windows平台分片集群部署

    本文转载自:https://www.cnblogs.com/hx764208769/p/4260177.html 前言-为什么我要使用mongodb 最近我公司要开发一个日志系统,这个日志系统包括很多 ...

  5. JDK 8 - Method Reference 分析

    Java SE 8 在 Java 语言层面上新增了 lambda expression 的功能,使得 Java 具备了函数式语言的能力 - 可以将函数作为方法参数传递,即 code as data. ...

  6. 如何利用JConsole观察分析Java程序的运行并进行排错调优_java

    如何利用JConsole观察分析Java程序的运行并进行排错调优_java 官方指导  use jconsole use jmx technology

  7. sys模块的问题,深浅COPY的应用场景,元祖与购物车程序练习-打印彩色\033[31;1m--------\033[0m

    打印彩色:%s为变量,格式化 print("Added %s into shopping cart,your current balance is \033[31;1m%s\033[0m&q ...

  8. WebApi 返值的实体值前缀加了个下划线

    发现MODEL类中加了个可以被序列化的标记,把它去除即可. [Serializable]

  9. Jsp+Servlet+JDBC的使用复习

    最近对JDBC进行了复习,对事物的理解,连接池的使用等部分都有一个复习,所以使用Servlet+JDBC完成了一个小Demo,在这里对这种底层的操作进行总结.框架的使用的确方便了我们的开发,但是底层的 ...

  10. 【洛谷】P3919 【模板】可持久化线段树(主席树)

    题目 传送门:QWQ 分析 主席树的模板,囤着 代码 #include <bits/stdc++.h> using namespace std; ; ], rs[N*], root[N*] ...