第八章 DP

本文探讨了多种动态规划在解决实际问题中的应用,包括寻找最大子序列和、最佳彩色带选择、01背包问题变形、PAT计数问题以及快速排序优化。通过详细解析每个问题的解决方案,展示了动态规划在不同场景下的高效性和灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1007.最大子序列和 **

在这里插入图片描述

在这里插入图片描述

  • DP集合划分:由于f[i]表示以i为右端点的所有区间集合,该集合可以分为两类,即只有i一个点,以及f[i-1]和i
  • 如何确定答案的左右端点?
  1. 此过程可在DP过程中完成
  2. 考虑DP的转移过程,每次我们只在f[i-1]+w[i]和w[i]中取最大值,只要f[i-1]<0,那么f[i]就取w[i],否则进行累和所以每次更新成w[i]都是对之前区间的截断,也就是说我们找到的区间之间不相交
  3. 那么只要在每次截断时保存该区间左端点,并在res需要更新时更新左右端点即可
#include<iostream>
#include<cstring>

using namespace std;

const int N=1e4+10;

int f[N],num[N];

int n;

int main(){
    
    cin>>n;
    
    for(int i=1;i<=n;i++)cin>>num[i];
    
    memset(f,-0x3f,sizeof f);
    
    int l,r,res=-1;
    
    for(int i=1,start ;i<=n;i++){
        
        if(f[i-1]<0){             //前区间使得和变小,截断
            
            f[i]=num[i];
            
            start=i;               //保存当前区间起始位置
            
        }
        
        else f[i]=f[i-1]+num[i];
        
        if(f[i]>res){              //res变大,更新起始端点和结尾端点
            
            res=f[i];
            
            l=start,r=i;
        }
    }

    if(res<0)res=0,l=1,r=n;
    
    cout<<res<<" "<<num[l]<<" "<<num[r];
}


1045.最佳彩色带

在这里插入图片描述

在这里插入图片描述

  • 集合的划分
  1. 不包含i和j此时从f[i-1,j-1]转移
  2. 包含i但不包含j此时从f[i,j-1]转移(注意f[i,j-1]涵盖了包含i但不包含j的情况)
  3. 包含j但不包含i此时从 f[i-1,j]转移
  4. 同时包含i和j,由于i可以匹配多次,所以i不仅可以匹配j,也可匹配1~j-1中的元素,此时从f[i,j-1]+1转移

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=210,M=1e4+10;

int n,m,l;

int f[N][M];

int p[N],s[M];

int main(){
    
    cin>>n;
    
    cin>>m;
    
    for(int i=1;i<=m;i++)cin>>p[i];
    
    cin>>l;
    
    for(int i=1;i<=l;i++)cin>>s[i];
    
    f[0][0]=0;
    
    for(int i=1;i<=m;i++)
       for(int j=1;j<=l;j++){
           
           f[i][j]=max(f[i-1][j],f[i][j-1]);
           
           if(p[i]==s[j])f[i][j]=max(f[i][j],f[i][j-1]+1);
           
       }
       
    cout<<f[m][l];
}

1068.找更多硬币

在这里插入图片描述

  • 01背包问题的变形

在这里插入图片描述

  • 由于本题需要对状态进行回推,并且要求按照字典序由小到大输出选择方案
    所以先从大到小排序,以便再倒推的时候做出最小选择
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=1e4+10,M=110;

int n,m;

bool f[N][M];

int num[N];

int main(){
    
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)cin>>num[i];
    
    sort(num+1,num+n+1,greater<int>());
    
    f[0][0]=true;
    
    for(int i=1;i<=n;i++)
       for(int j=0;j<=m;j++){
           
           f[i][j]=f[i-1][j];
           
           if(j>=num[i])f[i][j] |=f[i-1][j-num[i]];
       }
    
    if(!f[n][m])cout<<"No Solution";
    
    else {
        
        int i=n,j=m;
        
        bool is_first=true;
        
        while(i){
        
            if(j>=num[i] && f[i-1][j-num[i]]){
                
                if(is_first)is_first=false;
                
                else cout<<" ";
                
                cout<<num[i];
                
                j-=num[i];
            }
            
            i--;
        }
    }
}

1093.PAT计数

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

  • 状态机模型
  • 当字符相等s[i]==p[j]时,表示可以从上一个状态转移,统计一共可以转移到状态3的路径数量
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=1e5+10,mod=1e9+7;

char s[N],p[]=" PAT";

int f[N][4];

int main(){
    
    cin>>s+1;
    
    int n=strlen(s+1);
    
    f[0][0]=1;           //起始状态路径为1
    
    for(int i=1;i<=n;i++)
       
       for(int j=0;j<=3;j++){
           
           f[i][j]=f[i-1][j];
           
           if(s[i]==p[j])f[i][j]=(f[i][j]+f[i-1][j-1])%mod;
       }
       
    cout<<f[n][3];
}

1101.快速排序

在这里插入图片描述

  • 预处理出左边数的最大值以及右边数的最小值
#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

const int N=1e5+10;

int L[N],R[N];      //左边数的最大值,右边数的最小值

int a[N],n;

int main(){
    
    cin>>n;
    
    vector<int> res;
    
    for(int i=1;i<=n;i++){
        
        cin>>a[i];
        
        L[i]=max(L[i-1],a[i]);
    }
    
    R[n+1]=0x3f3f3f3f;
    
    for(int i=n;i>=1;i--){
        
        R[i]=min(R[i+1],a[i]);
    }
    
    for(int i=1;i<=n;i++){
        
        if(a[i]>L[i-1] && a[i]<R[i+1])res.push_back(a[i]);
    }
    
    if(!res.size())cout<<0<<endl<<endl;
    
    else {
        
        cout<<res.size()<<endl;
        
        cout<<res[0];
        
        for(int i=1;i<res.size();i++)cout<<" "<<res[i];
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值