zoukankan      html  css  js  c++  java
  • 对最长公共子序列(LCS)等一系列DP问题的研究

    LIS问题:
    (f[i])为以(a[i])结尾的最长上升子序列长度,有:

    [f[i]=f[j]+1(j<i&&a[j]<a[i]) ]

    可以用树状数组优化至(O(nlogn))

    基于排列的LCS问题((a,b)均为排列,即一个元素不会出现多次):
    (pos_i)(a_i)(b)中出现的位置,即(a_i=b_pos_i)
    (a)的一个子序列(a_p_1,a_p_2,...,a_p_m)(a,b)的公共子序列等价于(pos_p_1<pos_p_2<...<pos_p_m)
    求一个LIS即可。

    一般LCS问题:

    1. 经典解法:
      (f[i][j])表示只考虑(a)中前(i)个,(b)中前(j)个的最长公共子序列长度,有:

    [f[i][j]=left{ egin{aligned} & f[i-1][j-1] & a[i]=b[j]\ & max(f[i-1][j],f[i][j-1]) & a[i]!=b[j]\ end{aligned} ight.]

    十分简单,但是还有一种稍微复杂但是拓展性更高的做法:

    设$f[i][j]$表示只考虑$a$中前$i$个,$b$中前$j$个并且$b_j$已经和$a_1,...,a_i$中的某一个匹配的最长公共子序列长度,有:
    

    [f[i][j]=left{ egin{aligned} & f[i-1][j] & a[i]!=b[j]\ & max(f[i-1][k]+1) & a[i]==b[j],k<j\ end{aligned} ight.]

    为什么说这样拓展性更好?来看这样一道题
    题目要求最长上升公共子序列,不能直接用LCS的经典解法了,但是我们仔细思考一下,发现如果我们用上面的转移方程,我们只需要在从(f[i-1][k])转移到(f[i][j])时,只需要保证(b[k]<b[j])即可,所以我们得到新的转移方程:

    [f[i][j]=left{ egin{aligned} & f[i-1][j] & a[i]!=b[j]\ & max(f[i-1][k]+1) & a[i]==b[j],k<j&&b[k]<b[j]\ end{aligned} ight.]

    又因为当(a[i]==b[j])时,(b[k]<b[j])等价于(b[k]<a[i]),在转移枚举(j)时对所有(b[k]<a[i])(f[i-1][k])记录一个前缀(max)即可。
    代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define N 5007
    int f[N],a[N],b[N];
    int main()
    {
    	int i,j,n;
    	scanf("%d",&n);
    	for(i=1;i<=n;i++)
    		scanf("%d",&a[i]);
    	for(i=1;i<=n;i++)
    		scanf("%d",&b[i]);
    	int maxx=0,ans=0;
    	for(i=1;i<=n;i++)
    	{
    		maxx=0;
    		for(j=1;j<=n;j++)
    		{
    			if(b[j]==a[i])f[j]=max(f[j],maxx+1);
    			else if(b[j]<a[i])maxx=max(maxx,f[j]);
    			ans=max(ans,f[j]);
    		}
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    当然还有对于一般LCS问题的(O(nlogn))解法(不严格),同样可以拓展至此题。

    在排列中,(a)中的每一个元素唯一对应(b)中的一个元素,但在一般的LCS问题中不是这样,一个元素可以对应多个元素。

    怎么办呢?我们把(a)中每个元素在(b)中对应位置的集合拿出来,比如(a={3,3,2,4},b={2,3,3,5}),那么(a)中元素(3)对应的位置集合就是{2,3},将每个元素对应位置降序排列,再放回原序列中,对得到的新序列做一个LIS,就是最长公共子序列的长度,比如上面那个例子,a中每个元素对应集合为({2,3},{2,3},{1},{}),得到的新序列就是(3,2,3,2,1),它的最长上升子序列是(2,3),即(a,b)的最长公共子序列是由(b_2,b_3)组成的,这时再套用LIS的(O(nlogn))做法即可,但是这样复杂度不是严格(O(nlogn))的,因为每个(a)中的元素最多对应(n)个b中的元素,最坏复杂度达到(O(n^2 log n))

    至于这种做法到上面这题的拓展,因为你要保证你在(a)中选择的元素在(b)中的位置是递增的,同时要保证这些元素的值本身也是递增的,也就是这样:

    [f[i]=f[j]+1(j<i&&pos[j]<pos[i],a[j]<a[i]) ]

    发现是转移一个二维偏序关系,二维数点即可,复杂度是不严格的(O(nlog^2 n))

  • 相关阅读:
    SpreadJS 复制行
    RookeyFrame 模块 线上创建的模块 迁移到 线下来
    RookeyFrame 附件 上传附件
    RookeyFrame 字典 新增和绑定
    RookeyFrame Bug 表单管理 -> 查看表单 ->编辑字段页面 JS报错
    Catalan数
    美元汇率
    5倍经验日
    二分查找的边界问题
    线段覆盖5
  • 原文地址:https://www.cnblogs.com/lishuyu2003/p/11329708.html
Copyright © 2011-2022 走看看