蓝桥杯真题题解三

本文介绍了五个使用动态规划解决的编程题目,包括在矩阵中按照特定规则行走的方案数、最小测试次数确定手机耐摔指数以及利用同余优化计算特定区间数目的问题。此外,还讨论了如何确定包子铺中无法凑出的包子数量,涉及数论和完全背包问题。

一、走方格(DP,2020)

题目描述

在平面上有一些二维的点阵。 这些点的编号就像二维数组的编号一样,从上到下依次为第 1 至第 n 行, 从左到右依次为第 1 至第 m 列,每一个点可以用行号和列号来表示。 现在有个人站在第 1 行第 1 列,要走到第 n 行第 m列。只能向右或者向下走。 注意,如果行号和列数都是偶数,不能走入这一格中。 问有多少种方案。

输入格式

输入一行包含两个整数 n, m。

输出格式

输出一个整数,表示答案。

评测用例规模与约定

对于所有评测用例,1 ≤ n ≤ 30, 1 ≤ m ≤ 30。

思路

让我们先画一个以下的图来直接分析。

  1. 集合的状态表示还是比较好想的,因为我们是在一个二维矩阵中走方格且题目对行编号和列编号都有限制,所以状态表示是一个二维的。

  1. 属性:一般题目问的是什么,我们的属性就是什么。本题问的是方案的数量,则属性就是方案的数量。

  1. 状态转移:先假设我们走到了一个一般的点,如f[i][j];一般我们进行状态转移的时候都是考虑其最后一步怎么走,即是从哪个地方走到f[i][j]这个点的。题目要求说,走方格时我们只能往右或往下走。因此,f[i][j]有两种可能,一种时从它的上方f[i - 1][j]向下走到f[i][j];另一种是从它的左边f[i][j - 1]向右走到f[i][j]。

  1. 状态计算:f[i][j] = f[i - 1][j] + f[i][j - 1]。

  1. 这道题目还要求如果行号和列号都为偶数的话,我们不能走入这一格中,因此我们只需要在枚举的时候特判以下就能解决这个问题。

  1. 最后,我们需要考虑一下初值的问题。f[1][1] = 1。

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

using namespace std;

const int N = 50;

int n, m;
int f[N][N];

int main()
{
    cin >> n >> m;
    
    f[0][1] = 1;
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
        {
            if(i % 2 == 0 && j % 2 == 0) f[i][j] = 0;
            else f[i][j] = f[i][j - 1] + f[i - 1][j];
        }
    
    cout << f[n][m] << endl;
    
    return 0;
}
/*
f[0][1] = 1;
f[1][1] = f[1][0] + f[0][1]; 
*/

二、矩阵(DP,2020)

题目描述

把 1 ∼ 2020 放在 2 × 1010 的矩阵里。要求同一行中右边的比左边大,同一 列中下边的比上边的大。一共有多少种方案?

答案很大,你只需要给出方案数除以 2020 的余数即可。

思路

这道题目就比上面那道题目要稍微难想一点,因此我们可以先模拟一下,在矩阵中填数的这样一个过程。

为什么5只有一个位置摆放了?

因为题目要求,同一行中右边的比左边的大,下边比上边大。设想一下,假设5摆放在图中蓝色圈圈的下面,那么还有哪一个比5小的数能摆在它的上面。

由这个图我们可以抽象出一个规律:

a. 当上面摆放的数的数量和下面摆放的数的数量一样多的时候,下一个数只有一种选择;则当上面摆放的数的数量多余下面摆放的数的数量时我们的下一个数有两种选择。

b. 且上面摆放的数的数量一定是会大于等于下面的数的数量的,即在我们摆放的过程中,下面的数是永远都不可能比上面的数多的。

c. 不知道有没有小伙伴注意到,在模拟的过程中,我们是按1~n的顺序依次摆放这些数的。我觉得这题告诉我们,我们必须得先手动模拟一下,且在模拟的过程中,不是随便模拟的,而是遵循着一定的规律。不然,即使我们确实是在手动模拟,也很难推出问题的本质。

虽然我们图中画了两类集合,但是在某些情况下,并不是两类集合都存在的。

首先,需要明确j <= i的。

  1. 当j < i时,我们有两种位置可以选择。因此,f[i][j] = f[i - 1][j] + f[i][j - 1]。

  1. 当j = i时,也就是说我们摆放完最后一个位置后,j和i相等了,因此这个数只可能摆放在下面那一行。因此,f[i][j] = f[i][j - 1]。

:上面的分析中我们是在进行正推,而此处我们需要考虑最后一步,是在倒推,大家不要混淆了。

初始化操作:

f[0][i] = 0, f[i][0] = 1;

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

using namespace std;

int f[1011][1011];

int main()
{
    //初始化
    for(int i = 1; i <= 1010; i ++ ) f[i][0] = 1;
    
    for(int i = 1; i <= 1010; i ++ )
        for(int j = 1; j <= i; j ++ )
        {
            if(i == j) f[i][j] = f[i][j - 1];
            else f[i][j] = (f[i - 1][j] + f[i][j - 1]) % 2020; 
        }
    
    cout << f[1010][1010] << endl;
    
    return 0;
}

三、测试次数(DP,2018)

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

X 星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。

各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。

X 星球有很多高耸入云的高塔,刚好可以用

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值