动态规划专题(备战蓝桥杯国赛1/10)

学习目标:

        通过本篇学习,可以提高你对动态规划的理解。


学习内容

目录

学习目标:

学习内容

数字三角形模型

摘花生

方格取数(传纸条问题)

最长上升子序列模型

最长上升子序列

登山

背包问题

完全背包问题

多重背包问题Ⅱ

背包问题求具体方案



数字三角形模型

摘花生

样例:

2
2 2
1 1
3 4
2 3
2 3 4
1 6 5

运行结果:

8
16

        

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace  std;
const int N = 110;
int dp[N][N];//表示走到第i行第j列拿到花生粒数
int map[N][N];//这个表示第i行第j列花生的颗粒数
int t,row,col;
int main(){
    cin>>t;
    while(t--){
        cin>>row>>col;
        for(int i=1;i<=row;i++)
            for(int j=1;j<=col;j++)
                cin>>map[i][j];//将数据读入
        /*
        因为每次到(i,j)这个格子,只能是从(i-1,j)或者(i,j-1)这两个格子上其中一个赖的,所有
        dp[i][j]=max(dp[i-1][j],dp[i][j-1])+map[i][j]
        */
        for(int i=1;i<=row;i++)
            for(int j=1;j<=col;j++)
                dp[i][j]=max(dp[i-1][j],dp[i][j-1])+map[i][j];
        //dp[row][col]就是我们想要的值
        cout<<dp[row][col]<<endl;
    }
    return 0;
}

方格取数(传纸条问题)

测试数据:

8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0

运行结果:

67

解析:
    这个题我想到了两种思路:
    第一种思路就是分两种情况,先求出去的时候的最大值,然后再倒着找到路径,将这个路径上的所有数都置为0,然后再进行第二次的去。
    第二种思路就是去和回来看作是两次都去且是同时进行,这样就是一个三维的dp问题,(i1,j1,i2,j2)但是我们可以发现,可以同时进行,只要知道走的总步数和第1,2次的横坐标,就能确定两次行走当前的位置在哪,然后就可以简化成三维(k,i,j)。当两个趟走到同一个格子的时候,只需要加一次这个格子的值。最后在分四种情况进行讨论即可
#include <iostream>
using namespace std;
const int N=12;
int dp[2*N][N][N];//这个分别表示走了k步,i表示去时的横坐标,j表示回来时的横坐标。但我们可以直接看成两次都是是去B
int map[N][N];
int main() {
    int n;//n表示有n组数据
    cin>>n;
    int a,b,c;
    while(cin>>a>>b>>c,a||b||c) map[a][b]=c;
    //下表直接从1开始,这样就不用再进行预处理了
    for(int k=2;k<=2*n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++){
                int i1=k-i,j1=k-j;
                if(i1<1||i1>n||j1<1||j1>n) continue;//判断下标是否越界,如果越界了就不再进行判断了,直接进行下一次操作
                int t=map[i][i1];
                if(i!=j) t+=map[j][j1];//如果两次横坐标相同,就相当于两个人在同一个未知,所以加一次就行了,第二个人去的时候该位置的=权值就是0
                int &w=dp[k][i][j];
                w=max(w,dp[k-1][i-1][j]+t);//两个人现在的未知都有可能是从上面或者左边赖的,所以总共有四种情况,列举下来求最大值即可
                w=max(w,dp[k-1][i][j-1]+t);
                w=max(w,dp[k-1][i][j]+t);
                w=max(w,dp[k-1][i-1][j-1]+t);
            }
    cout<<dp[2*n][n][n]<<endl;
    return 0;
}

最长上升子序列模型

最长上升子序列

测试数据:

7
3 1 2 1 8 5 6

运行结果:

4

题目解析:
    
 dp[i]是指以i结尾的最长上升子序列的长度。
每次增加一个数,就要从前往后遍历一下,找
到小于当前数的所有数,求出这些数的dp的最
大值再加一就是以当前数结尾的最长上升子序
列。要注意求的是最长上升子序列,所以你不
知道最长上升子序列是以那个数结尾的,你要
将以所有数为结尾的最大值求出来就是本题的
答案。
注意:最长上升子序列并不一定是在这一组数中连续的
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int dp[N];
int s[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>s[i];
    for(int i=1;i<=n;i++) dp[i]=1;//因为有本身,所以dp的所有初始值都为1
    for(int i=1;i<=n;i++)
        for(int j=1;j<i;j++){
            if(s[j]<s[i]){
                //这就证明第j个大于当前数字,而dp[j]表示的就是前i的最长单调递增子序列,dp[i]=max(dp[j]+1,dp[i])
                dp[i]=max(dp[i],dp[j]+1);
            }
        }
    int cmax=-1;
    //因为最长子序列的结尾不确定,所以我们要从头遍历一遍,找到那个位置是最长子序列的结尾
    for(int i=1;i<=n;i++)
        cmax=max(cmax,dp[i]);
    cout<<cmax<<endl;
    return 0;
}

登山

测试数据:

8
186 186 150 200 160 130 197 220

测试结果:

4

解题思路:
    因为是在登山,所以从那个海拔开始下山是不确定的,
需要找到在那个点开始下山。因为上山既有上又有下的过程,所以我们就可以采取一个策略,
就是先求一遍从头开始最长上升子序列,再求一段从尾
部开始的最长上升子序列,然后这两段结合,求出最佳答案即可
//
// Created by 小魏 on 2023/5/31.
//

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010;
int s[N],dp1[N],dp2[N];
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>s[i];
    for(int i=1;i<=n;i++){
        dp1[i]=1;
        dp2[i]=1;//因为自身,所以最长上升子序列的最小长度位1
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<i;j++)
            if(s[i]>s[j]) dp1[i]=max(dp1[i],dp1[j]+1);
    for(int i=n;i>=1;i--)
        for(int j=n;j>i;j--)
            if(s[i]>s[j]) dp2[i]=max(dp2[i],dp2[j]+1);

    //因为最后求得是浏览经典最多,所以要找的是从那个位置下山
    int cmax=-1;
    for(int i=1;i<=n;i++){
        int x=dp1[i]+dp2[i]-1;//因为下山位置上的景点只能算一次,而两次dp都用了该点,所以要减去一个景点
        cmax=max(cmax,x);
    }
    cout<<cmax<<endl;
    return 0;
}

背包问题

完全背包问题

测试数据:

4 5
1 2
2 4
3 4
4 5

运行结果:

10

题解:

 下面这个试子进行推导:
     dp[i][j]=max(dp[i-1][j](这个的意思就是不拿物品),dp[i-1][j-v]+w,dp[i-1][j-2v]+2w,dp[i-1][j-3v]+3w....)
 	dp[i][j-v]=max(dp[i-1][j-v],dp[i-1][j-2v]+w,dp[i-1][j-3v]+2w,dp[i-1][j-4v]+3w......)
 综上:dp[i][j]=max(dp[i-1][j],dp[i][j-v]+w);由于j>j-v,所以正着便利即相当于是第i组,这样就可以用滚动数组优化成以下
 	由01背包问题我们可以得到
 	dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
 	完全背包问题得到的结论
 	dp[i][j]=max(dp[i-1][j],dp[i][j-v]+w);
 	所以优化后,正着循环就相当于dp[i]

//
// Created by 小魏 on 2023/5/31.
//

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010;
int dp[N];//表示拾取前i个物品之后背包剩余的体积为j(但是可以优化成一维,因为上一排的数据全都是依赖于下面一排)
int num,vol;//分别表示
int w[N],v[N];
int main(){
    cin>>num>>vol;
    int a,b;
    for(int i=1;i<=num;i++){
        cin>>a>>b;
        v[i]=a;
        w[i]=b;//将体积和价值放到数组当中
    }
    for(int i=1;i<=num;i++)
        for(int j=v[i];j<=vol;j++)
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    cout<<dp[vol]<<endl;
    return 0;
}

多重背包问题Ⅱ

测试数据:

4 5
1 2 3
2 4 1
3 4 3
4 5 2

运行结果

10

题解:
   多重背包问题就是在0-1背包问题上加了数量,因为数量是明确的,
所以只需要加一层循环(即数量的循环),试出拿多少个物品最后的价值最大。
 0-1背包问题的时间复杂度是 数量*背包体积。多重背包问题的时间复杂度就
是 数量*体积*物品数量.
    以上方法在本题当中的时间复杂度为:1000*2000*2000,很明显会超时,
所以我们要使用二进制来优化
 二进制优化思路:
    我们发现,将任意数N分成 1,2,4 ... x ,x-N这样一串数字,这一串
数字就能表示1-N当中的任意一个数,所以我们可以把每个物品都进行"分堆操作"
,将其分成1,2,4 ... x ,x-N这几堆,然后再求一个0-1背包问题。这样的时
间复杂度为 数量*体积*以二为低(物品数量)的对数.就不会再超时了
//
// Created by 小魏 on 2023/5/31.
//
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=20010;
int dp[N];
int w[N],v[N];
int num,vol;
int main(){
    int a,b,c;
    cin>>num>>vol;
    int n=0;//表示当前物品分到第n堆
    for(int i=1;i<=num;i++){
        cin>>a>>b>>c;
        int k=1;//当前物品分的当前堆的数量
        while(c>=k){
            n++;
            v[n]=k*a;
            w[n]=k*b;
            c-=k;
            k=k<<1;
        }
        //接下来还有最后一部分没有分堆,将最后一部分分堆
        if(c){
            n++;
            v[n]=c*a;
            w[n]=c*b;
        }
    }
    //数据预处理完毕,接下来就是求一下0-1背包
    for(int i=1;i<=n;i++)
        for(int j=vol;j>=v[i];j--)
           dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    cout<<dp[vol]<<endl;
    return 0;
}

背包问题求具体方案

测试数据:

4 5
1 2
2 4
3 4
4 6

运行结果:

1 4

解体思路:
    先求一下0-1背包问题,然后再逆着寻找一遍即可;
    注意:求0-1背包的时候不能简化成一维,要记录整个dp的状态
//
// Created by 小魏 on 2023/5/31.
//

#include <iostream>
#include <algorithm>
#include <algorithm>
using namespace std;
const int N=1010;
int dp[N][N];
int w[N],v[N];
int num,vol;
int main(){
    cin>>num>>vol;
    for(int i=1;i<=num;i++) cin>>v[i]>>w[i];
    for(int i=num;i>=1;i--)//倒着求,方便后面的逆向操作
        for(int j=0;j<=vol;j++)
        {
            dp[i][j]=dp[i+1][j];
            if(j>=v[i])
                dp[i][j]=max(dp[i][j],dp[i+1][j-v[i]]+w[i]);
        }
    cout<<dp[num][vol]<<endl;
    int n=vol;//n表示背包内现在还有多少容量
    for(int i=1;i<=num;i++)
        if(n>=v[i]&&dp[i][n]==dp[i+1][n-v[i]]+w[i]){
            cout<<i<<" ";
            n-=v[i];
        }
    return 0;
}

              题目来自acwing

蓝桥杯是中国最具知名度和影响力的计算机竞赛,每年举办一次国赛和一次省赛。备考蓝桥杯国赛,可以借助CSDN这个技术社区,收获许多宝贵的资源和经验。 首先,CSDN是一个集合了大量优质的计算机技术文章、博客和问答的平台。在CSDN上,我们可以找到许多与蓝桥杯相关的题目分析、解题思路以及优秀代码分享。这些资源可以帮助我们加深对蓝桥杯题目的理解,提升解题的能力。同时,通过阅读其他选手的解题思路和代码,我们可以学习到其他思维的启发,提高自己的编程水平。 其次,CSDN上有许多蓝桥杯的培训课程和学习资料。这些课程针对蓝桥杯的各个竞赛项目,从基础知识到高级技巧都有详细的讲解和练习。通过参加这些课程,我们可以系统地学习和巩固所需的知识点,增加自己对蓝桥杯各类问题的解决思路和方法的掌握程度。 此外,在CSDN论坛中,还有很多热心的蓝桥杯爱好者及往届选手积极分享经验和答疑解惑。我们可以在CSDN论坛上提问,和其他选手讨论交流,共同解决自己遇到的问题。通过和其他选手的互动,我们可以更好地认识到自己的不足之处,从而有针对性地提升自己的能力。 综上所述,备考蓝桥杯国赛期间,CSDN可以提供大量的学习资源,帮助我们深入理解题目、学习解题思路和提高编程实力。通过合理利用CSDN的各种功能和资源,我们可以更有针对性地备战蓝桥杯国赛,争取取得更好的成绩。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小魏苦练算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值