动态规划(二)

线性DP

898.数字三角形

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

  • 状态方程表示为
  • f[i,j]=max(f[i-1,j]+a[i,j],f[i-1,j-1]+a[i,j])
#include<iostream>
#include<algorithm>
#include<limits.h>

using namespace std;

const int N=510,INF=1e9;

int f[N][N];            //存状态方程

int a[N][N];           //存初始三角形

int main(){
    int n;
    
    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;
       
}

895.最长上升子序列

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

  • 状态转移方程 f[i]=max(f[j])+1,j=0,1,2...,i-1并且满足a[i]>a[j]
#include<iostream>
#include<cstring>

using namespace std;

const int N=1010;

int f[N];

int a[N];

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]这一个数,最大子序列长度为1
        
        for(int j=1;j<=i-1;j++)
        if(a[i]>a[j])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;
}


896.最长上升子序列II

在这里插入图片描述

  • 注意到数据范围上升到1e6,所以要优化原来的O(n2)做法
  • 我们观察原算法的冗余操作
  • 以样例数据为例
  • 发现在若干长度为1的最大子序列中(3,1,2…)1的优先度最高,即倘若枚举长度为2的最大子序列时,如果某数可以与3拼接,那么一定可以与1拼接,而如果可以与1拼接,却不一定可以与3拼接
  • 也就是说,在长度相同的最大子序列当中,我们只保留尾数最小的那个序列
  • 可以证明,由此求出的若干最长子序列的尾数是单调递增的
  • 那么当数a[i]进行拼接时,只需要在长度为1,2,3,...的若干最长子序列中,找到a[i]>=子序列尾数并且可以拼接的最长子序列即可
  • 因为最长子序列数组尾数单调递增,所以查找满足条件的最大尾数只需要二分一次即可,所以时间复杂度为O(nlogn)
#include<iostream>

using namespace std;

const int N=100010;

int q[N];                     //q[i]存储长度为i的最长子序列的尾数

int a[N];

int main(){
    
    int n;
    
    cin>>n;
    
    for(int i=0;i<n;i++)cin>>a[i];
    
    q[0]=-1e9;                   //因为数组中有负数,所以初始化为最小负数防止第一个值不更新而死循环
    
    int len=0;
    
    for(int i=0;i<n;i++){
        
        int l=0,r=len;
        
        while(l<r){                     //找左边界,即小于等于a[i]的第一个值
            
            int mid=l+r>>1;
            
            if(q[mid]>=a[i])r=mid;
            
            else l=mid+1;
        }
        
        if(a[i]>q[l]){                  //如果a[i]大于找出的左边界,则更新len的长度,并且更新对应的尾数(len有可能不变长,但q[l+1]尾数一定变小或不变,如果不 变小或不变,那么与找的尾数是小于a[i]的最大值冲突)
            
            len=max(len,l+1);
        
            q[l+1]=a[i];
        }
        
        else q[l]=a[i];    //如果a[i]等于找出的左边界,则len一定不可能变长,并且q[l]的尾数值变小或不变
    }
    
    cout<<len;
    
}

897.最长公共子序列

在这里插入图片描述

在这里插入图片描述

  • 本题关键在于集合的划分
  • 根据a[i,j]是否出现在集合中分成四种情况
  • 其中f[i-1,j] f[i,j-1]可以包含01,10的情况
    并且四种情况的最大值可能会有重叠,但由于最后是求最大值所以没有影响
  • 注意由于00这种情况会被包含在01,10情况之内,所以一般忽略不写
#include<iostream>
#include<cstring>

using namespace std;

const int N=1010;

int f[N][N];

char a[N],b[N];

int main(){
    
    int n,m;
    
    cin>>n>>m;
    
    cin>>a+1>>b+1;              //字符串下标从1开始
    
    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);     //如果a[i]==b[j],更新长度
       }
       
    cout<<f[n][m];
}

902 .最短编辑距离

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

#include<iostream>

using namespace std;

const int N=1010;

char a[N],b[N];

int f[N][N];              //将a[1~i]变成b[1~j]所需要的操作数的最小值

int main(){
    
    int n,m;
    
    cin>>n>>a+1>>m>>b+1;
    
    for(int i=0;i<=m;i++)f[0][i]=i;    //a[0]变成b[1~i]需要i步操作
    
    for(int i=0;i<=n;i++)f[i][0]=i;    //a[1~i]变成b[0]也需要i步
    
    for(int i=1;i<=n;i++)
       for(int j=1;j<=m;j++){
           
           f[i][j]=min(f[i][j-1]+1,f[i-1][j]+1);     //默认增删
           
           if(a[i]!=b[j])f[i][j]=min(f[i][j],f[i-1][j-1]+1);   //如果a[i]和b[j]相同则需要最后一步操作,否则不需要
           
           else f[i][j]=min(f[i][j],f[i-1][j-1]);
       }
       
    cout<<f[n][m];
    
}

899.编辑距离

在这里插入图片描述

  • 时间复杂度分析
  • 一共有n个给定的字符串,处理m次请求,nm上限均为1000,处理一对模板串和目标串的时间复杂度是O(n^2^),这里模板串和目标串的长度上限均为10,故时间复杂度是100
  • 对于m次请求,都要处理n对字符串,每次处理的时间复杂度是100,故总的时间复杂度为 1000 ∗ 1000 ∗ 100 = 1 0 8 1000 *1000 * 100=10^8 10001000100=108 可以直接计算
#include<iostream>
#include<string.h>

using namespace std;

const int N=15,M=1010;

char c[M][N];                     //存放给定的若干字符串

int f[N][N];

int n,m;

int edit_distance(char a[],char b[]){                     //最短编辑距离
    
    int la=strlen(a+1),lb=strlen(b+1);
    
    for(int i=0;i<=lb;i++)f[0][i]=i;
    
    for(int i=0;i<=la;i++)f[i][0]=i;
    
    for(int i=1;i<=la;i++)
       for(int j=1;j<=lb;j++){
           f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
           
           f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]));
        }
       
     return f[la][lb];
}

int main(){
    
    cin>>n>>m;
    
    for(int i=0;i<n;i++)cin>>c[i]+1;
    
    while(m--){                         //处理m次询问
        
        int lim,res=0;
        
        char tar[N];                    //目标串
        
        cin>>tar+1>>lim;
        
        for(int i=0;i<n;i++)
            if(edit_distance(c[i],tar)<=lim)res++;      //小于等于lim,res++
        
        cout<<res<<endl;
    }
}

区间DP

282.石子合并

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

  • 状态表示成将第i堆石子到第j堆石子合并成一堆石子的最小代价
  • 根据状态方程我们要枚举所有长度为j-i+1的区间,求出每个区间合并成一堆区间的最小代价
  • 状态的计算根据最后一步区间的划分,由于无论如何划分区间,最后一步所需要的代价是固定的
    等于该区间的前缀和s[r]-s[l-1],所以当前区间的最小代价只取决于划分出的两个区间f[i,k]+f[k+1,j]的代价和
  • 我们从长度len=2~n枚举区间后续计算需要用到较小区间的值,且len=1的区间代价为0不需要枚举
  • 对于长度为len的所有区间,枚举k=i~j-1,将每个区间的状态值f[l][r]计算出
#include<iostream>

using namespace std;

const int N=310;

int s[N];               //前缀和

int f[N][N];

int main(){
    
    int n;
    
    cin>>n;
    
    for(int i=1;i<=n;i++){
        cin>>s[i];
        
        s[i]+=s[i-1];
    }
    
    for(int len=2;len<=n;len++)                   // 枚举所有长度的区间
    
       for(int i=1;i+len-1<=n;i++){              //枚举该长度区间所有起止位置l,r
           
           int l=i,r=i+len-1;
           
           f[l][r]=1e9;                         //初始化改区间代价为一最大值
           
           for(int k=l;k<=r-1;k++){             //对于该区间,再枚举k种划分情况,计算最小代价f[l][r]
               
               f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
           }
       }
       
    cout<<f[1][n];
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值