AcWing 897. 最长公共子序列(线性dp LCS问题)

在这里插入图片描述
在这里插入图片描述

题意:

给定两个字符串,求两串的最长公共子序列(LCS问题)

思路:

直接上dp分析:

f[i,j] 状态表示:

集合:所有 a[1 ~ i]b[1 ~ j]公共子序列 的集合

属性:长度的最大值 max

状态计算:

集合划分

依据最后一个不同点,我们可以分为 4 类:

  • a[i]、b[j] 均存在于最长公共子序列(11)
  • a[i]、b[j] 均不存在于最长公共子序列(00)
  • a[i] 不存在,而 b[j] 存在(01)
  • a[i] 存在,而 b[j] 不存在(10)

对各类分别求得 max,汇总起来再取 max,即为 f[i,j] 的答案。

第 ① 类很好求,其存在的条件当然是 a[i]==b[j],根据定义,此类的最大值为:f[i-1,j-1] + 1

第 ② 类根据定义也很显然,为:f[i-1,j-1]

第 ③ 类就需要好好分析了:

先尝试用式子 f[i-1,j] 对该类进行计算,但 f[i-1,j]不完全等价于这一类,

因为此类所指的是:“a[i] 不包含,而 b[j] 包含”,

f[i-1,j] 表示:在 a 串的 1 ~ i-1 中 和 b 串的 1 ~ j 中 选择的最长公共子序列,一定不包含 a[i],可能包含 b[j] 也可能不包含,b[j] 的存在情况有两种,简而言之,f[i-1,j] 表示的集合可以分为 两类: “含 b[j]” 和 “不含 b[j]” 。

第 ③ 类集合实际上是与 f[i-1,j] 分的第一类b[j])是恰好完全等价

那有没有一个式子能够 恰好覆盖 (直接计算)这一类呢?我们发现是没有的。

但是我们知道,对于求一个集合的 数量属性 时,我们要保证 “不重不漏”,但是 求最值属性 的时候我们只要保证 “不漏” 即可,“不重” 其实是无所谓的,不影响最终的答案

因此我们再求第 ③ 类最大值的时候,我们就可以直接用 f[i-1,j] 来覆盖(注意:并不是直接计算出这一类),f[i-1,j] 表示集合完全包含第 ③ 类集合,且一定在 f[i,j] 表示的总集合内部

第 ④ 类,同理,分析和第 ③ 类同理,可以用 f[i,j-1] 表示。

我们发现,对于第 ② 类,它是 既包含于第 ③ 类,又包含于第 ④ 类,所以再求最大值时,只需要关注其它三类 即可。(优化之处)

这样一来,我们就可以 覆盖所有类,求得 整个集合的最大值(答案)

综上,我们得出 状态转移方程 f [ i , j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] , f [ i − 1 ] [ j − 1 ] + 1 ) f[i,j] = max(f[i-1][j],f[i][j-1],f[i-1][j-1] + 1) f[ij]=max(f[i1][j]f[i][j1]f[i1][j1]+1)三类,其中第 3 项在 a[i]==b[j] 时才存在)

时间复杂度:

O ( n m ) O(nm) O(nm)n、m 分别为两个字符串的长度)

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 1010;
int dp[N][N];
int n, m;
char a[N], b[N];

int main()
{
    cin>>n>>m>>a+1>>b+1;
    
    for(int i=1; i<=n; ++i)
    {
        for(int j=1; j<=m; ++j)
        {
            dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
            if(a[i]==b[j]) dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1); //当a[i]==b[j]第三类才存在
        }
    }
    
    cout<<dp[n][m]<<endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值