线性dp例题详解——数字三角形、最长上升子序列、最长公共子序列、最短编辑距离、编辑距离

数字三角形

1.状态表示dp[i][j]
i.集合:所有从起点走到(i,j)的路径
ii.属性:Max
2.状态计算
在这里插入图片描述
在这里插入图片描述
为了方便计算,如果出现i-1这样的下标,最好i从1开始。
动态规划问题时间复杂度一般等于状态数量×转移计算量
做法:自顶向上
也可以自底向上做

#include <iostream>
#include <algorithm>
using namespace std;
const int INF=1e9;
const int N=510;
int num[N][N];
int dp[N][N];
int main(){
    int n;
    scanf("%d",&n);
   for(int i=1;i<=n;i++){
       for(int j=1;j<=i;j++){
           scanf("%d",&num[i][j]);
       }
   }
   //初始化,相邻位置也要初始化
   for(int i=1;i<=n;i++){
       for(int j=0;j<=i+1;j++){
           dp[i][j]=-INF;
       }
   }
   //起点
   dp[1][1]=num[1][1];
   for(int i=2;i<=n;i++){//从2开始
       for(int j=1;j<=i;j++){
           dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+num[i][j];
       }
   }
   int ans=-INF;
   for(int i=1;i<=n;i++){
      ans=max(dp[n][i],ans);
   }
   printf("%d",ans);
   return 0;
}

最长上升子序列问题

动态规划问题中、
dp的维数越少越好
1.状态表示dp[N]
i.集合:以num[i]为结尾的最长上升子序列
ii.属性:集合里每一个上升子序列长度最大值
2.状态计算
分类:按第0~i-1个数中<num[i]的数分
dp[i]=max(dp[j],dp[i])+1
j∈(1,i-1)
在这里插入图片描述
最后遍历所有以i结尾最大值,找到其中最大的为最终结果
动态规划求方案:记录转移即可

#include <iostream>
#include <algorithm>
using namespace std;
const int N=1010,INF=1e9;
int num[N];
int dp[N];
int g[N];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&num[i]);
        dp[i]=1;
    }

    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++){
            if(num[i]>num[j]){
                  if(dp[j]+1>dp[i]){
                  dp[i]=dp[j]+1;
                  g[i]=j;
                }
            }
        }
    }
    printf("%d\n",g[3]);
    int res=0,j;
    for(int i=1;i<=n;i++){
        if(dp[i]>res){
            res=dp[i];
            j=i;
        }
        
    }
    printf("%d\n",res);
    
    
    //输出序列
    // while(g[j]!=0){
    //     printf("%d",num[j]);
    //     j=g[j];
    // }
    // printf("%d",num[j]);
    for(int i=0,len=dp[j];i<len;i++){
        printf("%d",num[j]);
        j=g[j];
    }
    
    return 0;
}

优化:把不同长度的以i为结尾的最长上升子序列的值存下来,随序列长度增加,结尾的值一定是严格单调递增的
给定以ai为结尾的数,找到比ai小的最大的数
在这里插入图片描述

#include <iostream>
using namespace std;
const int N=1e5+10;
const int INF=0x3f3f3f3f;
int q[N];//长度为i的最长上升子序列中最后一位数的最小值
int a[N];//
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)scanf("%d",&a[i]);
    
    q[0]=-INF;
    int len=0;
    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];//q[r+1]一定大于a[i]
    }
    printf("%d",len);
    return 0;
}

最长公共子序列

1.状态表示dp[N][N]
i.集合:在第一个序列的前i个字母中出现,在第二个序列的前j个子母中出现的子序列
ii.属性:max
2.状态计算
a[i]选或不选1/0
b[j]选或不选1/0

00
dp[i-1][j-1]
一般包含在01和10的情况中,不写
01
必须以子序列B[j]结尾,易简单理解为dp[i-1][j],但dp[i-1][j]不一定是以B[j]结尾的子序列,但一定包含01这种情况,我们可以用dp[i-1][j]表示这种情况,虽然有重叠,但没有关系,因为我们求的是最大值
求最大值最小值分成的子问题可以重复,求数量分成的子问题不能重复
10
必须以子序列A[i]结尾
11
必须包含A[i]和B[j]
dp[i-1][j-1]+1
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010;
int dp[N][N];
char a[N],b[N];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
   
    scanf("%s%s",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]=dp[i-1][j-1]+1;
        }
    }
    printf("%d",dp[n][m]);
    return 0;
}

最短编辑距离

在这里插入图片描述

在这里插入图片描述

#include <iostream>
using namespace std;
const int N=1010;
char A[N],B[N];
int dp[N][N];
int main(){
    int n,m;
    scanf("%d%s",&n,A+1);
    scanf("%d%s",&m,B+1);
    
    for(int i=1;i<=n;i++)dp[i][0]=i;//删除
    for(int i=1;i<=m;i++)dp[0][i]=i;//添加
    
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1);
            if(A[i]==B[j])dp[i][j]=min(dp[i-1][j-1],dp[i][j]);
            else dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1);//修改
        }
    }
    printf("%d",dp[n][m]);
    
    return 0;
}

编辑距离

在这里插入图片描述

#include <iostream>
#include <cstring>
using namespace std;
const int N=1010;
char A[N][10];
int dp[N][N];
int edited_distance(char str1[],char str2[]){
    
    int len1=strlen(str1+1);
    int len2=strlen(str2+1);
    for(int i=1;i<=len1;i++)dp[i][0]=i;
    for(int i=1;i<=len2;i++)dp[0][i]=i;
    
    for(int i=1;i<=len1;i++){
        for(int j=1;j<=len2;j++){
            dp[i][j]=min(dp[i-1][j],dp[i][j-1])+1;
            if(str1[i]==str2[j])dp[i][j]=min(dp[i][j],dp[i-1][j-1]);
            else dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1);
        }
    }
    return dp[len1][len2];
    
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",A[i]+1);
    }
    for(int i=1;i<=m;i++){
        char B[N];
        int t;
        scanf("%s%d",B+1,&t); 
        int res=0;
        for(int i=1;i<=n;i++){
            if(t>=edited_distance(A[i],B)){
                res++;
            }
        }
        printf("%d\n",res);
        
    }
   
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值