动态规划

这几天一直在刷蓝桥杯的题做准备,感觉蓝桥杯主要掌握dp,dfs,暴力枚举以及一些排序,并查集,kmp应该都能应付得来,期间也看到一些比较好的博客,下面就贴一个比较偏速成的博客

蓝桥杯复习总结
在这里插入图片描述
如果没刷题,对算法又是一脸懵那就死记硬背呗,大致的代码套路都差不多,不过个人不推荐。最好是理解算法,然后自己刷几道相关的题巩固一下,能自己独立写出代码来才是最重要的,这个只是为了有的时候写代码卡住能看模板找点灵感。

好了,正文开始

动态规划就是把复杂的问题拆开,分成较为简单的子问题一点点的解决,从而解决复杂的问题。

动态规划的几个要素为:

  • 最优子结构
  • 状态转移方程
  • 边界

对于不同的问题,我们如果确定可以用动态规划求解,那就要最先思考问题的边界,然后再确定状态转移方程。

来个动态规划最简单的例子
在这里插入图片描述
然后根据我们算法老师教的,画表格,最后是这个样子
在这里插入图片描述
然后想想这个问题的边界和状态转移方程

  1. 当没有金矿或者矿工人数不够一个矿也挖不了的时候,结果就是0
  2. 当只有一个金矿且人数够的时候,结果就是这个矿的金子数
  3. 当金矿有很多,但是最后一个矿的人数不够的时候,那结果就是表格中上一层的结果
  4. 有矿且人数够,那就取10个人挖四个矿和挖第五个矿且剩余人数还挖四个矿中的较大值作为更新。
    用代码表示大概是这样
    在这里插入图片描述
    我写dp的题的时候习惯把下标从1开始,比较切合实际,所以会没有上面的-1问题。这个主要还是看个人习惯
    把上面的条件变成代码
#include<iostream>
#include<algorithm>
using namespace std;

int n,m;//金矿数,工人数
int g[100],p[100];//金矿价值,所需劳动力
int res[100][100];

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>g[i]>>p[i];
    }

    if(n<=1&&m<p[1]){
        res[n][m]=0;
    }
    if(n==1&&m>=p[1]){
        res[n][m]=g[1];
    }

    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if(i<p[j]){
                res[j][i]=res[j-1][i];
                continue;
            }
            res[j][i]=max(res[j-1][i],res[j-1][i-p[j]]+g[j]);
        }
    }

    cout<<res[n][m]<<endl;
    return 0;
}

在这里插入图片描述

然后这个只是做一个引子,我是想分享刷题的时候遇到的一个比较有意思的dp

在这里插入图片描述
第一反应是最熟悉的走路方案问题,但是这个题还涉及到了宝贝价值和宝贝个数的限制。想了一下,普通的二维数组肯定没戏,所以这个题涉及到用四维数组。

大概思路:
假设d[i][j][t][c]代表到(i,j),手上t个物品,价值为c。
然后状态转移涉及到拿,还是不拿。

1.如果拿:那就要保证t小于题目给的k,而且新的价值大于手中任意物品的价值。
那么更新就是d[i-1][j][t-1][w[i][j] ] (从上边继承)+d[i][j-1][t-1][ w[i][j] ] (从左边继承)

2.不拿:d[i-1][j][k][c] (从上边继承)+d[i][j-1][k][c] (从左边继承)

边界条件:如果k=1的时候,就是只能拿一个物品,就先把边界设为1再进行更新。

代码:

#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;
ll d[55][55][13][13],mod=1000000007;
int a[55][55];


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

    //开始dp
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            for(int t=0;t<K+1;t++){
                for(int c=0;c<13;c++){
                    ll take=0,untake=0;
                    if(i==1&&j==1){
                        if(!t||(t==1&&c>a[i][j]))
                            d[i][j][t][c]=1;
                            continue;
                    }
                    //新的物品价值大于之前任意一个且有空间,就可以选择拿或者不拿
                    if(t&&c>a[i][j])take=d[i-1][j][t-1][a[i][j]]+d[i][j-1][t-1][a[i][j]];
                    untake=d[i-1][j][t][c]+d[i][j-1][t][c];
                    d[i][j][t][c]=(take+untake)%mod;   
                }
            }
        }
    }

    cout<<d[n][m][K][12]<<endl;
    return 0;
}

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

最后

dp虽好,使用前也要想好适不适合。比如数组结构很大的时候,n*m> 2 n 2^n 2n,那这个时候还不如用枚举。
另外,如果算法比较弱的话,推荐可以先看看小灰的漫画算法,java实现的,从基本的数据结构讲到排序,动态规划,A*……,漫画看起来理解轻松多了,我之前看这本书大概两个星期可以消化,它的拓展还是很有意思的(对了,我另外一个帐号天天签到,也没抽到一本实体书,呜呜呜)。要是进阶的话可以看看计算机算法这本书,这是我最近在看的,再配合着刷刷题总结一下,写代码套路都差不多,思想懂了实现起来不怎么难。当然如果是为了蓝桥杯这种比赛,还要学会点偷分技巧嘻嘻嘻。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

shelgi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值