【笔记】动态规划(3)---线性DP

本文详细介绍了四种使用动态规划解决的经典问题:数字三角形中寻找路径和最大和、最长上升子序列、最长公共子序列以及最短编辑距离。每种问题都包含了状态表示和状态更新的算法实现。
摘要由CSDN通过智能技术生成


线性DP

数字三角形 、最长上升子序列 、最长公共子序列、最短编辑距离


一、数字三角形

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5

状态表示

在第i层的第j个元素,包含该元素的路径上的数字的最大和
f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);

状态更新

  • 包含左上元素的路径上的数字的最大和
  • 包含右上元素的路径上的数字的最大和
#include<iostream>
using namespace std;

const int N=510, INF=1e9;

int n;
int a[N][N];
int f[N][N];


int main(){

    cin>>n;
    for (int i=1;i<=n;i++ )
        for (int j=1;j<=i;j++ )
            cin>>a[i][j];
    
    
    for(int i=0; i<=n;i++ )
        for(int j=0; j<=i+1;j++ )
            f[i][j]=-INF;

    
    f[1][1]=a[1][1];
    for(int i=2;i<=n;i++ )
        for(int j=1;j<= i;j++ )
            f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
    
    
    int res=-INF;
    for (int i=1; i<=n;i++ ) res = max(res,f[n][i]);
    cout<<res;
    return 0;
}

二、最长上升子序列

给定一个长度为 N的数列,求数值严格单调递增的子序列的长度最长是多少。

动态规划 O(n^2)

#include<iostream>
using namespace std;

const int N=1010;
int a[N];
int f[N];
int MIN=1e9;

int main(){
    int n;
    cin>>n;
    for(int i = 1;i <= n;i ++ ) cin>>a[i];
    for (int i = 1; i <= n; i ++ )
    {
        f[i] = 1; // 只有a[i]一个数
        for (int j = 1; j < i; j ++ )
            if (a[j] < a[i])
                f[i] = max(f[i], f[j] + 1);
    }
    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, f[i]);
    cout<<res<<endl;
    return 0;
}

状态表示

以第 i 个元素结尾的最长子序列的长度。

状态更新

  • 若元素能加入第最长子序列的结尾,则进行状态更新
  • f[i] = max(f[i], f[j] + 1);

动态规划+二分 O(n*logn)

#include<iostream>
#include<algorithm>

using namespace std;

const int N=100010;

int n;
int a[N];
int q[N];


int main(){
    
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    
    int len=0;
    q[0]=-2e9;
    
    for(int i=0;i<n;i++){
        int l=0,r=len;
        while(l<r){
            int mid=l+r+1>>1;
            if(q[mid]<a[i])l=mid;
            else r=mid-1;
        }
        len=max(len,r+1);
        q[r+1]=a[i];
    }
    
    cout<<len;
    return 0;
}

状态表示

长度为 i 的子序列的最小结尾元素。

状态更新

  • 在长度为 len 中,对不同长度的子序列的最小结尾元素进行状态更新,若子序列变长,则更新最长子序列长度。
  • q[r+1]=a[i];
  • len=max(len,r+1);

三、最长公共子序列

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

状态表示

以第i个元素为结尾、以第j个元素的为结尾的两个序列的最长公共子序列长度。

状态更新

  • 最长公共子序列包含第 i 个元素,包含第 j 个元素。被f[i-1][j-1] 包含,此时两个对应的新元素匹配,长度 + 1
  • 最长公共子序列不包含第 i 个元素,包含第 j 个元素。被f[i-1][j]包含。
  • 最长公共子序列包含第 i 个元素,不包含第 j 个元素。被f[i][j-1]包含。
  • 最长公共子序列不包含第 i 个元素,不包含第 j 个元素。被f[i][j-1]f[i-1][j]包含。

f[i][j]=max(f[i-1][j],f[i][j-1]);
if (a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1] + 1);

#include<iostream>
using namespace std;

const int N=1010;
char a[N],b[N];
int f[N][N];
int main(){
    int n,m;
    
    cin>>n>>m;
    for(int i=1;i<=n;i++)   cin>>a[i];
    for(int i=1;i<=m;i++)   cin>>b[i];
    
    for(int i=1;i<=n;i++) 
        for(int j=1;j<=m;j++){
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if (a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1] + 1);
        }
    cout<<f[n][m];
    return 0;
}

四、最短编辑距离

给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:
删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。

状态表示

以第 i 个元素为结尾的序列转变为以第 j 个元素的为结尾的序列的最少操作次数。

状态更新

  • 删除:前 i - 1 个元素与前 j 个元素相同。f[i-1][j]+1
  • 插入:前 i 个元素与前 j - 1 个元素相同。f[i][j-1]+1
  • 替换:前 i - 1 个元素与前 j - 1 个元素相同。f[i-1][j-1]+1
  • 对应元素相同则无需操作。

f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);

因为有边界问题,故单独考虑A为0个字符(只插入)和B为0个字符(只删除)的情况。

for(int i=0;i<=m;i++) f[0][i]=i;
for(int i=0;i<=n;i++) f[i][0]=i;

#include<iostream>
using namespace std;

const int N=1010;
char a[N],b[N];
int f[N][N];//存储所有操作方式的操作次数的最小值。


int main(){
    int n,m;
    cin>>n;
    for(int i=1;i<=n;i++)
    cin>>a[i];

    cin>>m;
    for(int j=1;j<=m;j++)
    cin>>b[j];
    
    for(int i=0;i<=m;i++)   f[0][i]=i;
    for(int i=0;i<=n;i++)   f[i][0]=i;
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
            if(a[i]==b[j])   f[i][j]=min(f[i][j],f[i-1][j-1]);
            else    f[i][j]=min(f[i][j],f[i-1][j-1]+1);
        }
    
    cout<<f[n][m];
    return 0;
}
  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值