首先介绍一下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)]的最大值。

参考代码:

 #include<cstdio>
#include<cstring>
int f[][],a[],b[],i,j,t,n1,n2,max;
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n1,&n2);
for(i=;i<=n1;i++) scanf("%d",&a[i]);
for(i=;i<=n2;i++) scanf("%d",&b[i]);
memset(f,,sizeof(f));
for(i=;i<=n1;i++)
{
max=;
for(j=;j<=n2;j++)
{
f[i][j]=f[i-][j];
if (a[i]>b[j]&&max<f[i-][j]) max=f[i-][j];
if (a[i]==b[j]) f[i][j]=max+;
}
}
max=;
for(i=;i<=n2;i++) if (max<f[n1][i]) max=f[n1][i];
printf("%d\n",max);
}
}

其实还有一个很风骚的一维的算法。在此基础上压掉了一维空间(时间还是平方)。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]的值好了。具体结合代码理解。

参考代码:

 #include<cstdio>
#include<cstring>
int f[],a[],b[],i,j,t,n1,n2,max;
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n1,&n2);
for(i=;i<=n1;i++) scanf("%d",&a[i]);
for(i=;i<=n2;i++) scanf("%d",&b[i]);
memset(f,,sizeof(f));
for(i=;i<=n1;i++)
{
max=;
for(j=;j<=n2;j++)
{
if (a[i]>b[j]&&max<f[j]) max=f[j];
if (a[i]==b[j]) f[j]=max+;
}
}
max=;
for(i=;i<=n2;i++) if (max<f[i]) max=f[i];
printf("%d\n",max);
}
}

最长公共上升子序列(LCIS)的平方算法@我们都爱刘汝佳

2011-2-18

LIS LCS n^2和nlogn解法 以及LCIS的更多相关文章

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

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

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

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

  3. LIS与LCS的nlogn解法

    LIS(nlogn) #include<iostream> #include<cstdio> using namespace std; ; int a[maxn]; int n ...

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

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

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

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

  6. 动态规划面试题基础合集1--数学三角形,LIS , LCS, CSD

    动态规划的一般思路是分为四步,即:寻找最优子结构.递归定义最优子结构.自底向上求解最优子结构和构造最优解. 接下来我列举出几个常见的动态规划面试题进行说明. (1)数学三角形:比较简单,直接贴一个我看 ...

  7. LIS,LCS,LICS 学习笔记

    1.最长上升子序列(LIS) 子序列: 1.可以不连续 2.相对位置不变 dp[i][j] 表示前i位置,最大值为j的LIS长度 1. dp[i-1][j] 前i-1位置,最大值为j的LIS长度 (没 ...

  8. LIS+LCS+LCIS

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

  9. dp入门(LIS,LCS)

    LCS

随机推荐

  1. UploadifyAPI-上传插件属性和方法介绍

    上一篇文章简单的介绍了Uploadify上传插件的使用.但是对于常用的属性和方法并没有说明.授人以鱼不如授人以渔,我决定将常用的属性列举出来,供大伙参考参考.           Uploadify属 ...

  2. struts2拦截器配置;拦截器栈;配置默认拦截器;拦截方法的拦截器MethodFilterInterceptor;完成登录验证

    struts2.xml 内容 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts ...

  3. Mac Maven java_home错误

    当maven装好之后出现 $ mvn -versionError: JAVA_HOME is not defined correctly. We cannot execute /usr/libexec ...

  4. (六)6.5 Neurons Networks Implements of Sparse Autoencoder

    一大波matlab代码正在靠近.- -! sparse autoencoder的一个实例练习,这个例子所要实现的内容大概如下:从给定的很多张自然图片中截取出大小为8*8的小patches图片共1000 ...

  5. HDU 产生冠军 2094

    解题思路:这题重在分析,可能你知道的越多,这题想得越多,什么并查集,什么有向图等. 事实是,我们会发现,只要找到一个,并且仅有一个的入度为0的点,说明可以找出   冠军.若入度为0的点一个都没有,说明 ...

  6. 【Android】跟着教程做の学习笔记

    教程 + <第一行代码 - Android> //尽量在十二月底学完吧(同步学习java基础)

  7. OpenGL ES之GLSurfaceView学习一:介绍

    原文地址::http://120.132.134.205/cmdn/supesite/?uid-5358-action-viewspace-itemid-6527 GLSurfaceView是一个视图 ...

  8. Android Studio Check for Update

    Android Studio 当前版本1.0.1, 官网新版本1.1.0, 通过 Check for Update...升级, 提示 Connection failed. Please check y ...

  9. mysql 1130 ERROR 1130: Host xxx.xxx.xxx.xxx is not allowed to connect to this MySQL server

    mysql -u root -p  mysql;use mysql;  mysql;select 'host' from user where user='root';  mysql;update u ...

  10. [转] DateTime.Now.ToString()的较为全面的使用介绍

    原文地址 DateTime.Now.ToString() 用法 具体的操作如下面的两段代码 //2008年4月24日 System.DateTime.Now.ToString("D" ...