动态规划系列 线性dp:最长公共子序列----中专生刷算法

做dp前先要了解dp分析法,不了解的朋友们去看之前的帖子

中专生刷算法:动态规划-CSDN博客

闫氏DP分析法

核心思想:从集合角度来分析DP问题

dp问题,在有限集合中求最值

为什么用dp?

因为集合中数量太多

dp为什么能解决数量太多的问题

用两个阶段

一阶段状态表示

化零为整状态表示----不是一个元素一个元素枚举,每次枚举一类元素,化成子集,用一个状态来表示

状态表示f(i)分为:集合定位和集合属性

二阶段状态计算

状态计算,化整为零

例如需要求f(i),f(i)是最大值,然后我们只需要将f(i)划分成集合

判断每个子集的最大值,哪个更大,哪个就是所有集合最大值

子集一般满足两个原则(不遗漏,不重复),有时候可以重复,但一定不能遗漏

集合的划分依据:寻找最后一个不同点

dp问题很多种

选择问题,线性DP,区间DP

这次是线性dp

点击主页更多dp写法

样题

最长公共子序列

给定两个长度分别为 N 和 M的字符串 A和 B,求既是 A的子序列又是 B的子序列的字符串长度最长是多少。

输入格式

第一行包含两个整数 N和 M。

第二行包含一个长度为 N的字符串,表示字符串 A。

第三行包含一个长度为 M的字符串,表示字符串 B。

字符串均由小写字母构成。

输出格式

输出一个整数,表示最大长度。

数据范围

1≤N,M≤1000

输入样例:
4 5
acbd
abedc
输出样例:
3

题目解析

最长公共子序列是无需连续的,例如样题中的两个序列

acbd

abedc

他们的最长公共子序列是abd

acbd

abedc

如果是最长子串,就是需要连续的

看到数据大小,联想y总的时间复杂度分析图

很容易想到是dp来做

闫氏dp分析法

先画思维导图,不清楚闫氏dp分析法的可以看之前的文章

在取max时,我们会发现,不选A[i],不选B[j]这种情况我们可以舍弃

因为都不选无法让子序列变得更长

而且,我们在选的时候,除了第四种情况,a[i],b[j]一定选了

其他情况实际上无确定a[i]是不是在最长公共子序列里

例如f(i,j-1),他的值实际上是a[i]到b[j-1]的最长公共子序列

在这个值里,不一定选择了a[i],但是这对我们的结果没影响

因为我们只需要最大长度即可

我们用一个图来讲解一下,例如两个字符串

A B C B D A B

B D C A B C

答案是

A B C B D A B

B D C A B C

图来源:b战:邋遢大哥233

带绿色箭头的,就是a[i]b[j]相等的情况

如果a[i]b[j]相等,就用我们思维导图上的f(i-1,j-1)

也就是f[i-1][j-1]+1,然后跟左边的f[i][j-1]和上边的f[i-1][j]做比较

取最大值

f[n][m]就是我们最后的答案,也就是4

#include<iostream>
using namespace std;
int f[1010][1010];
char a[1010],b[1010];
int main(){
    int n,m;
    //a+1的效果是从a[1]开始读字符串
    cin>>n>>m>>a+1>>b+1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            //判断上边和左边
            f[i][j]=max(f[i][j-1],f[i-1][j]);
            //判断a[i],b[j]是否相等
            //其实这里不需要max特判也可以通过
            //只要a[i]==b[j]
            //则f[i-1][j-1]+1一定大于等于f[i][j-1]或者f[i-1][j]
            //但是我不知道为什么,评论区有大神可以教教我
            //等我学会了再更新
            if(a[i]==b[j])f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
    }
    cout<<f[n][m];
    return 0;
}

2024/3/25 18:59更新

已经学会为什么只要a[i]==b[j]

则f[i-1][j-1]+1一定大于等于f[i][j-1]或者f[i-1][j]

因为f[i-1][j-1]+1,本身表示的就是f[i][j],这时,对于a,b(标红表示+1序列)序列来说

他们都是在末尾补了一个字符,i也+1,j也+1

那无论f[i][j-1]还是f[i+1][j],其中的a,b序列,都只能是和a,b序列相等或者是其子序列

例如a[i-1]一定是a[i]子序列,b[j-1]一定是b[j]子序列

a[i]b[j]的子序列的公共子序列长度

可能等于序列本身的公共子序列长度

但一定不会大于a[i]b[j]的公共子序列长度

所以a[i]==b[j]时,f[i-1][j-1]+1表示f[i][j],我们直接取序列本身的公共子序列长度即可

严谨版下图,比较难看懂,感兴趣的同学可以看看

代码优化

#include<iostream>
using namespace std;
int f[1010][1010];
char a[1010],b[1010];
int main(){
    int n,m;
    cin>>n>>m>>a+1>>b+1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i]==b[j])f[i][j]=f[i-1][j-1]+1;
            else f[i][j]=max(f[i][j-1],f[i-1][j]);
        }
    }
    cout<<f[n][m];
    return 0;
}

下篇继续做dp

动态规划的狂

dp的王

  • 52
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值